DataSet und Multithreading

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 15 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    DataSet und Multithreading

    Hallo,

    ich schreibe von mehreren Threads in ein DataSet, dass geht halt solange gut, wie nur wenige Änderungen sind.
    Sobald aber einige Änderungen kommen und auch neue Datensätze hinzugefügt werden, gibt es die ganze Bandbreite an Exceptions... :whistling:
    Hier mal mein Code in gekürzter Form, damit man versteht, wie der Vorgang ca. abläuft.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private _ctsLoad As CancellationTokenSource
    2. Private _poL As ParallelOptions
    3. Private Async Sub btnStatLoad_Click(sender As Object, e As EventArgs) Handles btnStatLoad.Click
    4. _ctsLoad = New CancellationTokenSource
    5. _poL = New ParallelOptions
    6. _poL.CancellationToken = _ctsLoad.Token
    7. _poL.MaxDegreeOfParallelism = Environment.ProcessorCount
    8. Dim z As New List(Of TestDBDataSet.TestTabelleRow)
    9. For Each row In TestDBDataSet.TestTabelle
    10. z.Add(row)
    11. Next
    12. Await Task.Run(Sub() Parallel.ForEach(z, _poL, AddressOf LoadData))
    13. End Sub
    14. Private Sub LoadData(row As TestDBDataSet.TestTabelleRow)
    15. _poL.CancellationToken.ThrowIfCancellationRequested()
    16. 'Load Data from XML...
    17. Dim Trefferliste = 'Prase the XML-Content into Trefferliste...
    18. If Trefferliste(0).Count = 1 Then
    19. If row.TestDatum <> CDate(Trefferliste(0).Datum.Value) Then
    20. row.TestDatum = CDate(Trefferliste(0).Datum.Value)
    21. Dim newLogRow = TestDBDataSet.Log.NewLogRow
    22. With newLogRow
    23. .Eintrag = "Neues Datum..."
    24. .Änderung = CDate(Trefferliste(0).Datum.Value)
    25. End With
    26. TestDBDataSet.Log.AddLogRow(newLogRow)
    27. End If
    28. End If
    29. End Sub

    Von daher ist es klar, dass es so noch nicht geht... Meine Idee wäre jetzt, dass die Threads die Änderungen ermitteln sollen und dann
    eventuelle Änderungen zurückmelden und im MainThread werden dann halt die ganzen Änderungen und Updates in einer Schleife in die
    Tabellen eingetragen. Nur leider komme ich an der Stelle nicht weiter.
    1) Was müsste von den Threads zurückgeliefert werden? Ich würde am liebsten direkt die fertige Row zurückgeben, allerdings bräuchte ich
    dann noch eine extra Unterscheidung was die Änderungen von Zeilen betrifft. Also einmal neue Zeile hinzufügen und einmal Zeileinhalt ändern.
    2)Wie würde der Programmaufbau aussehen? Wie könnte man es realisieren? ParallelForEach liefert ja nur den ParallelLoopState zurück.
    Ich müsste mir wohl eine Art List (Of Task (Of MyDataRow)) erstellen, die in einer Schleife abfeuern und dann irgendwie mit Task.WhenAny
    oder ContinueWith (sowas in der Art), die Änderungen/Updates übertragen.

    Eventuell gehts auch mit einer Art Progress (Of) wo ich dann zentral einen Sync Lock einbaue? Sowas in etwa?
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private MyDataSetLock As New Object
    2. Dim progressHandler = New Progress(Of MyDataRow)(Sub(value)
    3. Sync Lock MyDataSetLock
    4. 'Hier was ins DatSet schreiben... z.B. MyDataset.Test.AddNewTestRow(value)
    5. End SyncLock
    6. End Sub)
    7. Dim progress = TryCast(infoHandler, IProgress(Of MyDataRow))

    Also falls mir da wer weiterhelfen könnte, wie ich das threadsicher aufbauen könnte, wäre ich sehr dankbar.
    Auch hilfreiche Links, die Lambda Ausdrücke einfach erklären, wären mir hilfreich. Und auch wie diese Syntax dann immer geht, wo man
    Sub() und Function() etc alles in eine Reihe ballert... Problem ist halt der Aufbau dieser ganzen Verkettungen, ich weiß in etwa was ich brauche,
    ich kann es nur nicht in VB Code übersetzen. ^^
    Korrekt, allerdings nur bei einer Tabelle und diese wird ja wie im Bespielcode auch zeilenweise durchgegangen, also an jeder Zeile der Tabelle arbeitet max ein Thread.

    Daher können an einer Zeile nicht mehrere Änderungen stattfinden. Da sollte ich auf der sicheren Seite sein? Probleme gibt es halt beim anlegen der neuen Zeilen in anderen Tabellen.

    Habe es jetzt mal mit einem SyncLock probiert, wie im Eingangspost angedeutet, dass scheint zu gehen. Frage mich nur, ob es nicht mit dem ContinueWith oder so schöner gelöst wäre.

    Zumal man dabei auch eher ne Progressbar einbinden könnte, was bei beim Parallel.ForEach ja nicht so einfach ist.
    Nabend. Hatte jetzt nochmal Zeit um das mit dem SyncLock zu testen und es funktioniert fehlerfrei.

    Allerdings kommt es jetzt zu Fehlern bei den Datagridviews und Bindingssources, z.B. dass sie nicht selbst ihre Quelle sein können, und auch ArgumentNullExceptions, obwohl überall Werte drinne stehen. Meist passiert das, wenn ich z.B. die Anwendung minimieren und dann wieder aus dem Task raushole. Auch ist es so, dass wenn ich es aus dem Task rausholen, erstmal alles Grau ist und erst nach ein paar Sekunden baut sich das Bild wieder auf, so dass man die Tabellen etc sehen kann. Das Programm hängt sich wohl kurz auf.

    Habe jetzt schon so Sachen wie BindingSource.RaiseListChangedEvents = False und nach der Aktualisierung wieder auf True gesetzt, gefolgt von einem BindingSource.ResetBindings(True). Dadurch wurden es zwar weniger Fehler, aber bekomme trotzdem noch welche.

    Hätte jemand eine Idee wie ich diese Fehler abstellen bzw. umgehen kann?
    Jo, das sind im Grunde unzulässige Threadübergreifende Zugriffe auf Controls, wenn Nebenthreads auf einem Dataset rumorgeln, wo Controls dran gebunden sind.
    Normal wirft das ja eine Exception, aber bei Databinding kanner das wohl nicht verfolgen.

    K.A., was du machst, wenn ich vor einer langen Verarbeitung die BindingSources deaktiviere, und danach reaktiviere, dann krieg ich keine Exceptions.

    Evtl. auch zusätzlich DataTable.Begin-/End-Update + Dataset.EnforceConstraints wieder anschalten
    Also "Dataset.EnforceConstraints" finde ich, nur DataTable.Begin-/End-Update, da weiß ich nicht wo ich das finde bzw. wie ich das anwenden soll. Habe es mit Bindingscource, Dataset, Dataset+Table und Datagridview durchprobiert, aber nirgends finde ich den Befehl, weshalb ich davon ausging, dass Begin/EndEdit gemeint war.

    Bei der DataTable-Klasse finde ich auch nichts was so heißt. Könnte ich bitte ein Beispiel bekommen, wie es anzuwenden ist?
    Ich verwende das in meine Dataset-Helpers ziemlich auf LowLevel-Ebene. Das dürfte als Beispiel nicht sonderlich instruktiv sein, da in anderem Zusammenhang, und auch bisserl komplizierter.

    Aber gib doch einfach mal BeginUpdate im ObjectBrowser ein.

    VisualStudio richtig nutzen (Google ist nicht deine Mami)

    rdmguy schrieb:

    Meist passiert das, wenn ich z.B. die Anwendung minimieren und dann wieder aus dem Task raushole. Auch ist es so, dass wenn ich es aus dem Task rausholen, erstmal alles Grau ist und erst nach ein paar Sekunden baut sich das Bild wieder auf, so dass man die Tabellen etc sehen kann. Das Programm hängt sich wohl kurz auf.

    Habe die Ursache für den Fehler gefunden. Es lag daran, dass ich bei allen DataGridViews AutoSizeRowMode auf AllCells eingestellt hatte. Dadurch kam es zu dem absturzähnlichen Verhalten. Seitdem machts keine Mukken mehr.

    ErfinderDesRades schrieb:

    DataTable.Begin-/End-Update

    Auch wenn das Problem gelöst ist, würde gerne noch den Befehl "DataTabel.BeginUpdate" finde. Das auf dem Bild ist alles was ich angezeigt bekomme, wenn ich nach BeginUpdate suche. Oder meinst du DataTabel.BeginLoadData/EndLoadData. Das sind die beiden einzigen Befehle die dem nahe komme und die ich in der Klasse DataTable finde konnte.