Eine seltsame Exception

  • VB.NET
  • .NET (FX) 4.0

Es gibt 45 Antworten in diesem Thema. Der letzte Beitrag () ist von sonne75.

    Fa Microsoft erzählt mir: msdn.microsoft.com/de-de/libra….datatable(v=vs.110).aspx
    Datatable: Dieser Typ ist bei Multithread-Lesevorgängen sicher. Sie müssen alle Schreibvorgänge synchronisieren.

    Ein Beitrag in StackOverFlow sagt zu DataTable and thread safety: stackoverflow.com/questions/67…tatable-and-thread-safety
    If you are exposing the datatable via data-binding, then forget it; you cannot make that thread-safe


    Das bedeutet ja wohl: Selber machen, evtl. mit BackgroundWorker? Keine Erfahrung damit.

    EDIT:
    WTF kann ich hier im Text dem Link einen namen geben, wie im alten Board? Danke für jede Hilfe.

    sonne75 schrieb:

    weil es ja so selten auftritt...
    Ja, das ist typisch für die gemeinsten Fehler, die Threading verursacht, man nennts glaub auch die Race-Condition:
    Fast immer gehts gut, wenn mehrere Threads auf einer Auflistung rumorgeln, nur manchmal wenn grad superblöd das Dgv die Tabellenwerte abruft, und genau da fügt der annere was dazwischen, und das löst nu grad ganz unglücklich die neu-alloziierung der DataTable aus, weil die sich vergrößern muss... sodass das Dgv also Daten aus einer Speicherstelle zieht, wo sie eiglich nicht mehr sind...

    Threading ist regelrecht gefürchtet.

    us4711 schrieb:

    Das bedeutet ja wohl: Selber machen, evtl. mit BackgroundWorker? Keine Erfahrung damit.

    Wieso Backgroundworker? Und was selber machen? Ich dachte eher an:

    sonne75 schrieb:

    Ich müsste in dem Fall die Methode in die GUI rausführen und von da wieder aufrufen (Event wird schon sowieso gesendet, ich bräuchte an der Stelle nur die Methode aufrufen).


    Die Kommunikation auf dem Bus läuft eben im Nebenthread, die Datensätze werden aber im GUI-Thread dann erstellt. Die Datensätzeaktualisierung klappt ja gut (von den vorhandenen).

    @us4711
    EDIT: Wenn ich gerade dein Zitat von MSDN nochmal lese, müsste ich dann alle Schreibvorgänge im Hauptthread machen?
    Ich habe jetzt noch mal gegoogelt, eigentlich will ich das gar nicht im GUI-Thread machen, die Kommunikation (und da werden die empfangenen Daten ins DataSet geschrieben) läuft u.U. andauernd, das würde vielleicht die GUI stören.

    Wie mache ich denn die Schreibroutinen threadsicher? Ich müsste überall in .Designer.vb-Datei Synlock verteilen, oder? Aber dann muss ich das bei jeder DataSet-Änderung neu machen, weil die .Designer.vb-Datei neu erzeugt wird...
    Also, ich hab's mal so gelöst:

    Auch bei mr DataSetOnly mit den Helferlein von EDR.

    Bei jedem Aufruf des Programmes in frmMain_Shown, oder auch von anderer Stelle die erforderliche Datenmanipulation in den Events eines Backgroundworkers (in Form_Load geht's nicht, k.A. warum)

    - frmMain_Shown Initialisierung des Backgroundworkers bw

    - in bw_DoWork das Abarbeiten der erforderlichen Manipulationen nach dem Schema:
    DataSet.StronglyTypedDataTable.StronglyTypedDataColumn=MyNewValue

    - in bw_RunWorkerCompleted dann Dataset.Save(Me)

    Das funktioniert auch, wenn parallel in andere Programmbereichen Updates zur gleiche Zeit laufen.
    Voraussetzung ist allerdings, das die entsprechenden Forms mit Dataset.Register(Me) registriert sind.

    sonne75 schrieb:

    Wie mache ich denn die Schreibroutinen threadsicher?
    letztlich immer mit Control.BeginInvoke.
    Was anners gibts nicht. Auch Synclock und Konsorten nützen nix, einzig Invoking transferiert den Vorgang in den Gui-Thread. Und das ist sehr nervig, weil das ist sehr teuer.
    Also sieht man zu, dass man c.BeginInvoke nicht zu oft aufruft, also bei MassenOperationen nicht jeden Piss transferieren, sondern immer gleich große Datenpakete schnüren und transferieren.
    Was das für eine ständig parallel laufende Kommunikation bedeutet, muss man wohl im Einzelnen gugge, so alle 500ms ein Control.BeginInvoke, da sollte sich noch keiner beklagen.
    Denkbar wäre zB was mit einer ConcurrentQueue, wo der NebenThread immer fröhlich reinschmeißt.
    Ein Gui-Timer könnte die dann alle 500ms ausleeren und ins Dataset verfüllen oder was auch immer.

    Alternativ zum Timer könnte der NebenThread nach dem Füllstand gucken, und die Leerung per Invoking beauftragen.

    Du kannst auch mal gugge msdn.microsoft.com/de-de/library/dd997305(v=vs.110).aspx

    (Ach guck - das ist jetzt doch ein Ansatz ohne Control.BeginInvoke)

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    ErfinderDesRades schrieb:

    Auch Synclock und Konsorten nützen nix, einzig Invoking transferiert den Vorgang in den Gui-Thread.

    Warum sollte Synclock nichts nutzen? Ich könnte doch das "AddValues", welches neue Datensätze in die "Value-Table" hinzufügt (da kommt immer diese Exception, wenn sie kommt), in "Synclock"-Rahmen nehmen. Und die anderen Value-Manipulationen, die ich habe, auch in die Klasse "Values" auslagern, wo ich dann alles mit "ValueTable" mache...

    Ich will es ja gar nicht im GUI-Thread machen, sondern nur sicherstellen, dass nichts parallel geschieht. Dafür müsste ich eben alles in eine Klasse führen und da locken. Oder?

    @us4711
    Ich habe leider gar nicht verstanden, was du meinst :(
    Wenn eine BindingSource per Databinding Werte aus der DataTable abruft - da ist mir derzeit keine Möglichkeit bekannt - einen Synclock-Fuß dazwischenzukriegen.

    Aber ich guck gleich mal nach, ob DataTable oder Dataset ein SynchronisizingObjekt anbietet, und ob man damit tatsächlich auch Databinding threadsicher kriegt.
    Aber bei MSDN steht, nur das Schreiben muss synchronisiert werden. Bei mir werden die Datensätze entweder geschrieben, dann kann User sie nur sehen, oder gelesen, dann hat sie User vorher geschrieben. D.h. es geschieht in meinem Fall kein doppelter Schreibzugriff vom User und Nebenthread.

    Die Exception, die ich hatte, war immer beim Initialisieren - als das Gerät erstmalig komplett ausgelesen wurde, und dazu einige Datensätze erstellt wurden, da wird die GUI von mir geblockt.
    Wo steht das?
    Also ich kann mir das nicht vorstellen, dass ein Lesen ungesperrt gehen soll. Etwa das Lesen ruft Count ab, aber dann haut der Schreibe-Thread dazwischen, und löscht paar Zeilen?
    Und Anfügen ist eiglich auch nicht besser - kann mir schon vorstellen, dass da ein Index beschädigt wird, wenner zu beginn des Lesens 100 Elemente Indiziert, und am Ende auf einmal 120.

    btw - was meinst du mit "Gui blocken"?
    naja - sobald ein DGV auffm Form ist, oder auch nur eine BindingSource, werden Daten vonne DataTable abgerufen.
    Eine Möglichkeit, das Gui zu blocken fund ich mal mit BindingSource.RaiseListchangedEvent=False, weil glaub DataSource-Databinding fußt ganzngar auffm ListChanged-Event.
    Aber sicher bin ich nicht, ob das wirklich ausreicht.
    ach so, ja ich dachte, weil die Exception nur beim Initialisieren...

    Aber ist ja glaub falsch gedacht.

    Wie gesagt: Ich denke nicht, dass gute Idee ist, wenn der NebenThread ohne weitere Vorkehrungen Daten ins Dataset einfügt.
    Das muss man Invoken, oder mit ConcurrentQueue, oder mit RaiseListChanged.False oder was auch immer.

    Einer gebundenen DataTable was zufügen bedeutet letztendlich DGV.Rows.Add(wasAuchImmer).
    Und wenn du das direkt machst, ohne Databinding, hast du die CrossThread-Exception.
    Beim Databinding gibts diese Exception leider nicht, aber das heißt nicht, dass deswegen die CrossThread-Zugriffe sicher seien.

    So zumindest meine Theorie, genaueres weiß man erst, wenn du dein Problem gelöst hast.

    ErfinderDesRades schrieb:

    ach so, ja ich dachte, weil die Exception nur beim Initialisieren...

    Ach so, du meintest nur für diesen einen Fall am Anfang auf False und am Ende auf True zu setzen? Zu welchem Zeitpunkt muss ich dann .ResetBindings() machen? Erst nach RaiseListChanged=True? Dann muss ich diesen Fall speziell abfangen, denn zur Zeit mache ich ResetBindings, wenn neue Daten angekommen sind.

    ErfinderDesRades schrieb:

    Wie gesagt: Ich denke nicht, dass gute Idee ist, wenn der NebenThread ohne weitere Vorkehrungen Daten ins Dataset einfügt.
    Das muss man Invoken, oder mit ConcurrentQueue, oder mit RaiseListChanged.False oder was auch immer.


    Das mit ConcurrentQueue habe ich nicht verstanden, inwiefern ich das auf DataSet umsetzen kann.

    Mein Ablauf:
    Beim Initialisieren wird der Nebenthread gestartet, der sich über Kommunikation mit dem Gerät kümmert, mehrmals pro Sekunde, wenn nötig. Jedes Mal, wenn neue Daten ankommen (also immer), wird DataSet beschrieben und danach .ResetBindings() aufgerufen (im BeginInvoke() natürlich).
    Sollte eine neue Zuordnung von Kanälen festgestellt werden (beim Initialisieren immer, weil ja noch nichts angelegt), dann werden erst teilweise Rows hinzufügt, bisher im Nebenthread, das kann ich aber leicht invoken. Ansonsten werden nur die Daten ins DS eingefügt, für Rows, die schon da sind. Nach dem AddXXRow() werden die gleichen Berechnungen wie für existierende Rows durchgeführt und DS beschrieben.

    Wenn ich alles, was mit DS zu tun hat, in GUI-Thread bringe, dann habe ich keine Vorteile von einem Nebenthread, denn es wird ständig GUI-Thread belastet... Ich wollte eigentlich, dass die ständigen Datenabfragen im Hintergrund laufen... So muss ich bisher nur jedes Mal .ResetBindings() machen, um Anzeige zu aktualisieren.
    noch einmal: Der NebenThread darf nicht ins Dataset schreiben, solange wie auch immer ein Databinding besteht.
    Das ist alles.
    Je länger ich drüber hirne, desto mehr Möglichkeiten fallen mir ein, jede mit Vor- und Nachteilen. Aber das wesentliche ist: NebenThread - nicht in ein gebundenes Dataset schreiben
    Wie du das bewerkstelligst ist egal.

    Der Ansatz mitte ConcurrentQueue ist, die Daten im Nebenthread nicht ins Dataset zu schreiben (hab ich das schon erwähnt? ;) ), sondern erst in dieser Queue zu sammeln.
    Und dann gelegentlich invoken, sodass der Gui-Thread die Daten ins DS einpflegt.

    Mein neuester Spleen ist übrigens, dass der NebenThread sein eigenes Dataset hält, von dem ein Klon (Dataset.GetChanges) dann gelegentlich ins Gui-Dataset eingemergt wird.
    Fürs Mergen gibts Extra-Befehle im Dataset.