Von BackgroundWorker zu Threading?

  • VB.NET

Es gibt 28 Antworten in diesem Thema. Der letzte Beitrag () ist von ray.

    Von BackgroundWorker zu Threading?

    Guten Morgen,

    bis dato bin ich beim Thema Threading immer recht gut mit dem Backgroundworker gefahren, die häufigsten Anwendungen waren die Überbrücken längerer Aufbauzeiten von Fenstern und Tabellen, sowie "Wartefenster" - wenn ein lang andauernder Prozess aufgerufen wurde.

    Nun stehe ich leider vor einem kleinen Problem, dass mir die Verwendung vom BGW verhindert.

    Hintergrund ist folgender:

    Ein größerer Datensatz wird aus einer Exceltabelle gezogen, angepasst und in einem Datagridview angezeigt. Das geschieht momentan in einem Backgroundworker, da je nach Anzahl der Daten, der Aufbau auch mal 30 Sekunden brauchen kann (während dessen gibt es visuelles Feedback in Form eines Wartebildschirms).

    Da der Inhalt der Datagridview durch den Backgroundworker entsteht, ist es nicht möglich das Clipboard im Bezug auf das Datagridview zu verwenden - was aber notwendig ist (Fehlermeldung: Für den aktuellen Thread muss der STA-Modus (Single Thread Apartment) festgelegt werden, bevor OLE-Aufrufe ausgeführt werden können. Stellen Sie sicher, dass die Hauptfunktion mit STAThreadAttribute gekennzeichnet ist.).

    Nun möchte ich anstelle eines Backgroundworkers zu Threading wechseln und stehe da noch vor ein, zwei Verständnisproblemen.

    Mein momentaner BGW Code sieht in etwa so aus:

    VB.NET-Quellcode

    1. ' Backgroundworker starten
    2. bgwLoadWindow.RunWorkerAsync()
    3. ' Wartebildschirm anzeigen
    4. winWaitingScreenLoadWindow.Show()
    5. ' -----------------
    6. Private Sub bgwLoadWindow_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwLoadWindow.DoWork
    7. ' Funktion zum Laden des Bildschirms aufrufen
    8. Testfunktion()
    9. End Sub
    10. Private Sub bgwLoadWindow_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgwLoadWindow.RunWorkerCompleted
    11. ' wenn das Fenster aufgebaut ist, den Wartebildschirm schließen
    12. If Not winWaitingScreenLoadWindow Is Nothing Then
    13. winWaitingScreenLoadWindow.Dispose()
    14. winWaitingScreenLoadWindow = Nothing
    15. End If
    16. End Sub



    Nun den BGW durch Threading ersetzt. Da ich im Prinzip nur einen Thread im Hintergrund brauche, dachte ich, ich kann es recht simpel halten:


    VB.NET-Quellcode

    1. winWaitingScreenLoadWindow.Show()
    2. Dim thread1 As New Threading.Thread(AddressOf Testfunktion)
    3. If Not winWaitingScreenLoadWindow Is Nothing Then
    4. winWaitingScreenLoadWindow.Dispose()
    5. winWaitingScreenLoadWindow = Nothing
    6. End If
    7. ' Thread starten
    8. thread1.Start()
    9. ' Auf Thread warten
    10. thread1.Join()


    Folgendes (Verständnis-)Problem:

    Ich muss ja irgendwie abfangen, wann der Thread beendet ist, um dann den Wartebildschirm zu schließen. Ich dachte das ist möglich via Join(). Das hält jedoch mein Hauptprogramm (bzw. den Code vom Hauptthread) an, weswegen der Wartebildschirm nicht angezeigt wird, sondern alles einfriert.

    Kann mir jmd da einen Denkanstoss geben, wie ich zum gewünschten Resultat komm?

    Danke und Grüße

    ray schrieb:

    Da der Inhalt der Datagridview durch den Backgroundworker entsteht, ist es nicht möglich das Clipboard im Bezug auf das Datagridview zu verwenden - was aber notwendig ist (Fehlermeldung: Für den aktuellen Thread muss der STA-Modus (Single Thread Apartment) festgelegt werden, bevor OLE-Aufrufe ausgeführt werden können. Stellen Sie sicher, dass die Hauptfunktion mit STAThreadAttribute gekennzeichnet ist.).

    also ich denke, wenn du die Daten in ein typisiertes Dataset einliest, dürfte sich das Problem in Luft auflösen.

    Aber mir ist auch vollkommen unklar, wozu du ausgerechnet im NebenThread was vom Clipboard wollen solltest.
    Notfalls kann man doch im hauptThread dem Clipboard die Daten entnehmen, und dann kann man doch sicher im Nebenthread damit herumwirbeln (also wenn das wirklich notwendig sein sollte)

    Frage: Begründemal, warum es notwendig ist "das Clipboard im Bezug auf das Datagridview zu verwenden". Oder erklärmalüberhaupt, was du damit meinst - das ist so eigenartig formuliert.
    also ich denke, wenn du die Daten in ein typisiertes Dataset einliest, dürfte sich das Problem in Luft auflösen.


    Ich muss dazu sagen, dass ich die Daten (um es mir einfacher zu machen) Zelle für Zelle einlese und in das Datagridview einfüge. Hintergrund sind die doch recht individuellen Ansprüche an die Darstellung der Tabelle. So muss nach bestimmten Angaben die Darstellungen bearbeitet werden, eine Zeile verdoppelt werden, neue Spalten unterschiedlicher Typen (ComboboxCell, Cell etc.) eingefügt werden etc.. Daher verwende ich kein Dataset, da mir ehrlicherweise keine Wege eingefallen sind, das Datagridview später so zu realisieren.


    Frage: Begründemal, warum es notwendig ist "das Clipboard im Bezug auf das Datagridview zu verwenden". Oder erklärmalüberhaupt, was du damit meinst - das ist so eigenartig formuliert.


    Gerne.
    Der Inhalt des Datagridview ist im Nebenthread entstanden. Wenn jetzt der Nutzer in einer Zelle Strg+C drückt um den Zellinhalt zu kopieren, stürzt das Programm mit der obigen Fehlermeldung ab. Der Nutzer muss aber in der Lage sein, die Inhalte kopieren zu können. Desweiteren möchte ich das auch noch via Contextmenu realisieren (was natürlich auch nicht möglich ist, da der Zugriff auf das Clipboard verweigert wird).
    Einfügen aus dem Clipboard in das Datagridview ist übrigens kein Problem (ich verstehe zwar nicht warum, aber es wird schon seine Gründe haben;)).

    ray schrieb:

    Der Inhalt des Datagridview ist im Nebenthread entstanden.

    Oh - das sollte eiglich schon beim Entstehen eine InvalidOperationException geben, weil das ist einfach nicht zulässig.
    Hast du vlt. CheckforIllegalCrossThreadAccess deaktiviert?

    Dann sind natürlich allerlei putzige und unvorhersehbare Fehler vorprogrammiert.

    Zum Design von DGVs - also ich kann da schon ziemlich viel mit anstellen - grad im Zusammenhang mit typ.Dataset.
    gugge Was können typisiertes Dataset und DatagridView? - Abschnitt Styling.

    Allerdings - Clipboard-Handling kann schon anspruchsvoll sein. Wenn die Daten aus was komplizierterem als aus lauter STrings bestehen - funzt das dann ühaupt noch?
    Also man kann doch nicht beliebigen Mist in ein DGV pasten, dessen 2.Spalte etwa ein DateTime sein muß.

    Oder pastest du immer nur in eine Zelle?
    Also das Programm zieht den Inhalt der Excel-Tabelle Zelle für Zelle und schreibt Zelle für Zelle in das Datagridview, zwei for Schleifen quasi. Das liegt daran, dass ich jede Excelzelle auf Darstellung prüfe (u.a. Schrift durchgestrichen, Hintergrundfarbe) und das Datagridview relativ individuell gestalte (manche Zeilen müssen doppelt auftreten, manche Zellen müssen zur Veränderung gesperrt sein, manche Zeilen benötigen eine einzelne ComboBoxCell - andere nicht usw.). Warscheinlich ist das mit DataSets möglich, allerdings hab ich damit anfangs schnell aufgegeben, da ich nicht weiterkam.

    Zwecks Clipboards. Das Problem im Detail: Es gibt Zellen im Datagridview, die zum editieren blockiert sind, man sie aber natürlich anklicken kann. Nun kann man nach Windows Standards via STGR+C was auch immer da dann selektiert wird ins Clipboard bringen. Dabei stürzt das Programm momentan mit o.g. Fehler ab. In Zellen die editierbar sind, ist nicht die ganze Zelle selektiert, sondern nur der Inhalt (vgl. wie wenn ich in excel einen Doppelklick auf eine Zelle mache und den Inhalt darin markiere) und das funktioniert problemlos.
    Mein Ziel ist es, dass bei Benutzen von nicht editierbare Zellen in Verbindung mit STGR+C deren Inhalt ins Clipboard kopiert wird.

    Codetechnisch sieht das momentan so aus:

    VB.NET-Quellcode

    1. Private Sub DataGridHead_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles DataGridHead.KeyDown
    2. If e.KeyCode = Keys.C Then
    3. If e.Control = True Then
    4. e.SuppressKeyPress = True
    5. Dim test As DataGridView = sender
    6. Dim test2 As DataGridViewSelectedCellCollection = test.SelectedCells
    7. Dim str As String = test2.Item(0).Value
    8. DataGridHead.Invoke(New CopyToClipboardHandler(AddressOf CopyToClipboard), str)
    9. End If
    10. End If
    11. End Sub


    (Ignorier die Variablennamen, die sind nur zu Testzwecken ;))

    Jetzt tritt der Fehler natürlich auch auf, da diesmal durch Code Zugriff aufs Clipboard genommen wird.

    Also dachte ich mir: Threading, irgendwie in den STA Modus kommen und dieses kleine Problem hoffentlich dann lösen ;).

    Hast du vlt. CheckforIllegalCrossThreadAccess deaktiviert?


    Wo wird das gesetzt / wo muss ich danach gucken?
    nee - also ein ExcelSheet nachbilden, das wird auch mit gebundenem DGV recht umfangreich.
    Weil dann ist eine ExcelZelle ja ein eigener Datensatz, mit Font, BackgroundColor, BorderStyles, und was einem noch sso alles einfällt.

    Ich denke allerdings, deine Abstürze rühren daher, dass du in einem Nebenthread im DGV herumfummelst. Wobeis wirklich verwunderlich ist, dasses erst bei StrgC abstürzt.
    Aber du hast noch kein code gezeigt, wie du im Nebenthread da DGV-Zellen reinbastelst, daher weißichnich, ob das nicht eine falsche Spur ist.

    Ähm

    VB.NET-Quellcode

    1. DataGridHead.Invoke(New CopyToClipboardHandler(AddressOf CopyToClipboard), str)
    was soll das?
    Ist

    VB.NET-Quellcode

    1. CopyToClipboard(str)
    nicht hinreichend?
    Nun... jetzt wo ich mir das nochmal anguck, hab ich was falsches behauptet. Nicht nur der Inhalt des Datagridviews wird im Backgroundworker erstellt - das gesamte Datagridview wird im Backgroundworker erstellt - warscheinlich liegt dann auch da ein nicht so guter Programmierstil vor...

    Also, das wird aufjeden Fall die Ursache sein. Die Daten, also auch die Zeilen und Spaltenanzahl der Datagrid entstehen durch den Backgroundworker - deswegen zickt das Clipboard, sobald man es im Bezug auf die Daten verwenden möchte.

    Warum das funktioniert, wenn ich mich im aktiven Editieren einer Zelle befinde weiß ich nicht, denn das wird alles irgendwie von Windows im Hintergrund geregelt (Strg+C, Strg+V).

    Mal ein kurzer Versuch mein Programm zu erläutern:

    VB.NET-Quellcode

    1. Private Sub bgwLoadWindow_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwLoadWindow.DoWork
    2. ' Funktion zum Laden des Bildschirms / Exportieren der Excel Tabelle in Datagridview aufrufen
    3. ExtractExcelData()
    4. End Sub
    5. Private Sub ExtractDataInExcel()
    6. ' hier wird ein Fenster geöffnet indem das Datagridview liegt
    7. ' sowie das Datagridview mit Daten gefüllt
    8. End Sub


    Das heißt, alles ist durch den BGW entstanden. War ursprünglich auch so gewollt, da der Fensteraufbau mitunter ne Weile braucht (nicht das Exportieren der Daten aus Excel). Hatte das zwischenzeitlich irgendwie verdrängt...


    was soll das?

    VB.NET-Quellcode

    1. CopyToClipboard(str)
    nicht hinreichend?


    Doch sorry, da hab ich einen meiner Versuche das hinzubekommen kopiert:

    Ursprünglich war der Code so wie von dir geschrieben -> Fehler kommt.
    Danach versucht via Delegate und Handler vom Thread getrennt auszuführen -> Fehler kommt.
    du müsstest v.a. man rauskriegen, was so lange dauert.
    Ists das Auslesen von Excel, so ist in NebenThread sinnvoll.

    Ists hingegen die Konstruktion des DataGridViews, nützt Threading nix, denn das DGV muß im MainThread gebildet werden.

    Also mal einen Datenabruf basteln ohne DGV-Konstruktion, und gugge, ob der lange dauert.
    Sowohl das Auslesen der Daten von Excel, als auch das erstellen des DTV dauern mir zu lange um den User mit einer gefrorenen Oberfläche zu konfrontieren. Wenns allerdings gar nicht anders gehen würde, müsste ich halt drauf verzichten da optisches Feedback zu geben.

    Gibt es jetzt direkt keine Möglichkeit durch Threading mit STAThreadAttribute ans Ziel zu kommen?

    Ich kann mir einfach nicht vorstellen, dass ich einen Text, der im Code vollkommen auswertbar ist, nicht irgendwie in die Zwischenablage bekommen kann - hin oder her durch welchen Thread das dazugehörige Objekt entstanden ist...
    Warum muss der in einem anderen Thread in die ZwAblage kopiert werden?
    Mache alles was lange dauert in einem Thread und der "liefert" am Ende das, was du in die ZwAblage kopierst (am Stück). Denn das reine kopieren in die ZwAbl dürfte ja wohl nicht so lange dauern oder?

    picoflop schrieb:

    Warum muss der in einem anderen Thread in die ZwAblage kopiert werden?
    Mache alles was lange dauert in einem Thread und der "liefert" am Ende das, was du in die ZwAblage kopierst (am Stück). Denn das reine kopieren in die ZwAbl dürfte ja wohl nicht so lange dauern oder?


    Hm... also vll reden wir auch ein bisschen aneinander vorbei. Der Thread kann ja nichts in der Art zurückgeben - falls du das meinst.

    Um mein Programm nochmal zu erläutern: Eine recht große Exceltabelle wird in einem Datagridview recht individuell dargestellt. Der Aufbau des DGV, sowie das exportieren der Excel Tabelle brauchen zu lange, als dass ich das Programm in der Zeit eingefroren haben möchte. Deswegen sind beide Funktionen in einem gemeinsamen BGW ausgelagert.

    Das bedeutet, dass das Fenster in dem der DGV liegt im BGW erstellt wurde, also nicht im Mainthread. Dadurch ist unter gewissen Bedingungen kein Zugriff auf das Clipboard möglich - im Code gar nicht (sobald man in den Zeilen rund um den DGV / der Form des DGV auf Clipboard zugreift, wird ein Fehler geworfen) und von Windows selbst lustiger Weise nur, wenn man in einer Textzeile Text markiert hat und STGR+C drückt (dann macht Windows das irgendwie im Hintergrund, so wie wenn du irgendwo beliebigen Text markiert und STGR+C drückst).

    Sooo... nun ist das Problem, dass mein DGV zu ca. 50 % aus nicht editierbaren Zellen besteht. Das heißt man kann nur die ganze Zelle selektieren, nicht aber deren Inhalt und kommt schon gar nicht erst in den (Text-)Editiermodus. Drückt man nun STGR+C und Windows nimmt in Hintergrund wieder Beschlag vom Clipboard wird ein Fehler geworfen.

    Und genau daran häng ich ;)


    Edit: Grade mal ein Gedankengang:

    Kann ich in einem Dataset das komplette Datagridview vorbereiten, sprich kann man in einem Dataset alle Zellen eines Datagridviews vorab definieren und dann ein leeres Datagridview damit füllen.
    Dann wäre ein Ansatz folgender: Im BGW das Dataset so konstruieren, wie momentan das fertige Datagridview. Im Hauptthread jedoch dann nur noch das Dataset in das Datagridview laden (was recht schnell gehen müsste).

    Meinungen dazu?

    ray schrieb:

    Der Thread kann ja nichts in der Art zurückgeben - falls du das meinst.

    Klar kann er. Wobei "Task" dann natürlich besser als "Thread" wäre.
    Es bleibt aber noch die Frage, warum du nicht einfach das ProgressChanged des BGW verwendest, denn schließlich es das GENAU dafür da?

    BTW: Mit den Async Funktionen aus dem Async CTP ("await") erledigt sich auch das Problem mit dem "warten" auf einen Thread, da durch Await/Async der UI Thread beim "warten" nicht einfriert.
    So... was genau soll er denn deiner Meinung nach zurückgeben? Woher weiß ich denn, welche von den 200 Zellen der User anklickt nachdem das Fenster erstellt wurde und der Thread beendet ist und deren Inhalt in die Zwischenablage kopieren möchte?

    Es bleibt aber noch die Frage, warum du nicht einfach das ProgressChanged des BGW verwendest, denn schließlich es das GENAU dafür da?


    Kannst du mir das bzgl. meines Problems näher erläutern?

    ray schrieb:

    Edit: Grade mal ein Gedankengang:

    Dassisja auch mein Gedankengang. Den Aufbau des DGVs kannst du in keiner Weise beschleunigen oder vom NebenThread aus übernehmen, aber du kannst ihn optimal vorbereiten, indem du im Nebenthread die erforderlichen Daten bereitstellst.

    Hast du mal überprüft, wo der Flaschenhals sitzt? Ists das Datenladen von Excel? - das kann in einen Nebenthread.

    Andernfalls ists offenbar der Aufbau des DGVs, und das kann nicht in einen Nebenthread.

    Für welche Art von Threading du dich entscheidest ist glaub egal: ob du nun Backgroundworker, AsyncWorker - CodeProject, Threads, Tasks oder das Async CTP nutzst.

    Wichtig ist erstmal zu klären, ob dem Einfrieren ühaupt mit Threading beizukommen ist.
    Joa hatte glaub in nem oberen Beitrag schonmal erwähnt, dass der Aufbau des DGVs sehr viel Zeit in Anspruch nimmt - das reine Exportieren der Daten aber auch. Hält sich ungefähr die Waage, ist jedoch jeweils meiner Meinung nach zu lange um es im Hauptthread zu lassen. Meine Hoffung zwecks Threading war ja, dass ich dann einfach das STAThreadAttribute für diesen Thread setzen kann und somit Zugriff aufs Clipboard habe.

    Dassisja auch mein Gedankengang.


    Wenn nun alles so gut wie es geht in einem Dataset vorbereitet und dann das Dataset in das DGV geladen wird, dauert dieser Vorgang dann ebenfalls länger oder geht das relativ schnell? Hast du da Erfahrungen mit gemacht?

    ray schrieb:

    Hast du da Erfahrungen mit gemacht?

    Jo, aber vmtl. nicht auf dich anwendbar.
    Ich benütze DGV ja immer mit Databinding, und für besondere Sachen auch mal mit ownerdrawn Zellen. Da gibts keine Probleme: Ich kann im Nebenthread Datenladen, und wenn fertig, im MainThread die Bindung erneuern - das geht ratzfatz.

    Aber du mußt da ja scheinbar immens viele DGVRows einfügen, und dann auf jeder einzelnen Zelle herumhühnern - all das geht nicht in einem NebenThread.
    Das maximale an Optimierung, was du leisten kannst, ist wie gesagt: die Daten mit allem drum und dran schnell zugreifbar bereitstellen, dass die Hühnerei auf den GridZellen wenigstens ein optimal schnelles Daten-Holen hat (sodass wirklich nurnoch das Gestalten der Zelle Performance frisst).
    Ich glaub das wird leider nichts, weil ich dann im Nachhinein z.B. nochmal checken müsste ob die Excelzelle durchgestrichen war oder nicht - schätze diese Info greift das Dataset nicht ab. Ob ich dann dieses Attribut checke oder gleich den ganzen Wert übernehm dürfte Laufzeit-technisch kaum einen Unterschied machen.

    Meinst du nicht, dass es möglich wäre, anstelle eines Backgroundworkers via Threading einen zweiten Thread zu erstellen, diesen Anstelle des Backgroundworkers aber mit dem STAThreadAttribute laufen zu lassen - sodass der Zugriff auf das Clipboard mögl. ist?

    ray schrieb:

    Ich glaub das wird leider nichts, weil ich dann im Nachhinein z.B. nochmal checken müsste ob die Excelzelle durchgestrichen war oder nicht - schätze diese Info greift das Dataset nicht ab.
    Naja, du wirst ein sehr spezielles Dataset brauchen, ich glaub sogar, eine eigene Datenstruktur wäre sinnvoller.
    Und dieser Datenstruktur wird im Nebenthread bereits mitgeteilt, ob die Excel-Zelle durchgestrichen ist.

    Also beim DGV-basteln nochmal in den Excel-Com-InterOp-Objekte herumzuwühlen - das kostet immens, könnte ich mir vorstellen.