SyncLock bei rekursivem Aufruf

  • VB.NET

Es gibt 17 Antworten in diesem Thema. Der letzte Beitrag () ist von WinDUser.

    SyncLock bei rekursivem Aufruf

    Hallo,

    ich stehe momentan vor einem recht unschönen Problem. Dieses möchte ich kurz abstrakt darstellen.

    Daten:
    Nehmen wir an, ich habe eine Klasse A mit den folgenden Methoden

    Public MethodeEins()

    Public MethodeZwei()

    Public MethodeDrei()

    Anforderung:
    Von der Klasse gibt es nur ein Objekt. Auf dieses Objekt kann jedoch aus mehreren Threads zugegriffen werden. Ich muss verhindern, dass irgend eine der Methoden zur gleichen Zeit doppelt läuft. Also Es dürfen weder MethodeEins und MethodeZwei, noch MethodeEins und MethodeEins durch unterschiedliche Threads aufgerufen werden.

    Normale Lösung:
    Ich setze in der Klasse ein Objekt Lock und schütze so mit SyncLock alle Methoden.

    Zusatzbedingung:
    Ich kann nicht ausschließen, dass bspw. MethodeEins im weiteren Stackverlauf doch noch MethodeDrei benötigt. Wenn also (indirekt) MethodeEins die MethodeX aufruft, soll dies möglich sein.

    Problematik:
    Mein SyncLock würde nun verhindern, dass MethodeDrei durch MethodeEins aufgerufen wird. Ich hätte ein schönes Deadlock.

    Frage:
    Gibt es ein Standardverfahren, dass es mir ermöglicht, dass sich die Methoden innerhalb eines sequenziellen Stacks (indirekt, also mit Methoden anderer Objekte dazwischen) aufrufen können, den Zugriff aus einem anderen Thread jedoch verhindern? Gewissermaßen würde ich ein Lock setzen, meinem Stack aber einen Schlüssel mitgeben, so dass der Prozess weiterhin Zugriff auf die Methoden hat ?( .

    Für Ideen und Anregungen wäre ich sehr dankbar :thumbup: !
    Du könntest Dir ein eigenes SyncLock basteln.

    Schau mal bei System.Threading.Thread.CurrentThread rein, ob Du die Thread-ID oder etwas ähnliches findest, woran Du einen Thread eindeutig identifizieren kannst.

    Dann machst Du das so:

    VB.NET-Quellcode

    1. Class Klasse
    2. Dim SyncObject As ProzessID 'Ich denke, das wird dann ein Integer sein.
    3. Dim StackCount As Integer = 0
    4. Sub Methode1()
    5. Dim CurrentThreadID = GetCurrentThreadID()
    6. Do Until SyncObject Is Nothing OrElse SyncObject Is CurrentThreadID
    7. 'Warten, bis anderer Thread draußen ist
    8. Loop
    9. SyncObject = CurrentThreadID
    10. StackCount += 1
    11. Methode2()
    12. StackCount -= 1
    13. If StackCount = 0 Then
    14. SyncObject = Nothing
    15. End If
    16. End Sub
    17. Sub Methode2()
    18. Dim CurrentThreadID = GetCurrentThreadID()
    19. Do Until SyncObject Is Nothing OrElse SyncObject Is CurrentThreadID
    20. 'Warten, bis anderer Thread draußen ist
    21. Loop
    22. SyncObject = CurrentThreadID
    23. StackCount += 1
    24. DoStuff2()
    25. StackCount -= 1
    26. If StackCount = 0 Then
    27. SyncObject = Nothing
    28. End If
    29. End Sub
    30. End Class



    Wenn es sich tatsächlich um einen Integer handelt solltest Du (0 und = anstelle Nothing und Is verwenden und) eine zusätzliche Boolean-Variable anlegen, die anstelle von "Do Until SyncObject Is Nothing" kommt, weil eine ThreadID theoretisch auch 0 sein könnte, was zu einem Konflikt kommen würde.

    Der StackCount wird benötigt, weil ansonsten ein Aufruf von Methode1 an Methode2 SyncObject wieder auf Nothing setzen würde, obwohl sich die Ausführung noch in Methode1 befindet.

    Man kann das ganze hin und her wahrscheinlich auslagern, aber dadurch steigt das Risiko einer Race-Condition.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Danke dir,

    das wäre in jedem Fall eine gute Lösung. Ich entnehme deinem handgebastelten Vorschlag, dass es keine fertige Lösung für die Situation gibt? Eigentlich seltsam, solche rekursiven Aufrufen innerhalb eines Threads sollten doch bei größeren Projekten nicht all zu selten sein (??).

    Ich werde mir dann überlegen, wie ich das von dir vorgeschlagene Vorgehen möglichst Allgemein halten kann.

    VB.NET-Quellcode

    1. Sub MethodeEins(ByVal Optional IndirectCall As Boolean = False)
    2. If Not SyncLock OrElse IndirectCall Then 'oder wie auch immer du das sonst abfragen willst
    3. 'bla
    4. End If
    5. End Sub
    6. 'Wenn du aus MethodeZwei nun MethodeEins aufrufen willst
    7. MethodeEins(True)
    Den Rest kannst du dann eigentlich so lassen, da die Übergabe von IndirectCall ja nur Optional und standardmäßig False ist.

    BjöNi schrieb:

    Den Rest kannst du dann eigentlich so lassen, da die Übergabe von IndirectCall ja nur Optional und standardmäßig False ist.
    Hi, das wird leider nicht funktionieren, da ich im voraus nicht abschätzen kann, wann diese Methode nochmals benötigt wird. Es ist möglich, dass zwischen MethodeEins und dem indirekten Aufruf von MethodeZwei beliebig viele andere Methoden liegen. Also so nach dem Motto:

    MethodeEins = EsseEis
    MethodeZwei = PassAufDasDuNichtErstickst

    EsseEis
    -> NehmeEisInDenMund
    --> LeckeUEberEis
    ---> BewegeZunge
    ----> PassAufDasDuNichtErstickst

    Aber danke dir für den Vorschlag!
    Es gibt anscheinend einen Helfer für die Situation.
    Ich glaube @ErfinderDesRades: hat mal was darüber geschrieben. Der Satz ging irgendwie "Das ist wie SyncLock, nur allgemeiner." Aber das ist schon etwas länger her.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Da hast Du recht ^^
    Ich hab aber das Gefühl, dass es um deses SyncLock ging. Aber ich kann mich auch irren.
    Mal sehen. Vielleicht haben andere noch Ideen.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    WinDUser schrieb:

    solche rekursiven Aufrufen innerhalb eines Threads

    innerhalb EINES threads gibts doch kein Problem mit Lock etc

    Die SyncLock-Anweisung stellt sicher, dass der Anweisungsblock nicht von mehreren Threads gleichzeitig ausgeführt wird. SyncLock hindert die einzelnen Threads solange daran, den Block zu aktivieren, bis er von keinem anderen Thread mehr ausgeführt wird.



    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    3. one()
    4. Debug.Print("done")
    5. End Sub
    6. Private lck As New Object
    7. Private k As Integer = 2
    8. Private Sub one()
    9. SyncLock lck
    10. Debug.Print("1")
    11. two()
    12. End SyncLock
    13. End Sub
    14. Private Sub two()
    15. SyncLock lck
    16. Debug.Print("2")
    17. three()
    18. End SyncLock
    19. End Sub
    20. Private Sub three()
    21. SyncLock lck
    22. Debug.Print("3")
    23. k -= 1
    24. If k > 0 Then
    25. one()
    26. Else
    27. k = 2
    28. End If
    29. End SyncLock
    30. End Sub
    31. End Class

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

    Hallo @picoflop,

    du meinst, das Lock erfährt implizit auf welchem Thread es registriert wurde und wird innerhalb dieses Threads ignoriert? Bist du dir da sicher?

    Die MSDN-Hilfe könnte ein eindeutiger Hinweis sein, oder nur eine Ungenauigkeit in der Beschreibung.

    Grüße

    WinDUser
    Hatte ich übersehen. Danke dir.

    edit: Läuft wunderbar. Tja vielen Dank für den Hinweis! Und ich hatte mich schon so auf eine fancy-Lösung gefreut ;)

    Also Fazit: SyncLock sperrt den Zugriff anderer Threads auf die entsprechenden Codestellen. Der Thread, der das Lock gesetzt hat, kennt auch den Schlüssel.

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

    Nun doch noch eine Frage.

    In deinem Beispiel steht ja soetwas:

    VB.NET-Quellcode

    1. Public Sub Eins()
    2. SyncLock lock
    3. Zwei()
    4. 'Marker
    5. End Lock
    6. End Sub
    7. Public Sub Zwei()
    8. SyncLock lock
    9. 'Do something
    10. End Lock
    11. End Sub



    Besteht nun an der Stelle 'Marker überhaupt noch das Lock? Immerhin wurde es ja in Zwei() aufgelöst!

    Beste Grüße vorweg.
    Synclock ist nur ein Wrapper für Monitor.Enter .Exit
    msdn.microsoft.com/de-de/library/vstudio/de0542zz.aspx
    Derselbe Thread kann Enter mehrmals ausführen, ohne dass er blockiert wird. Exit muss jedoch ebenso so oft aufgerufen werden, bevor die Blockierung anderer Threads aufgehoben wird, die auf das Objekt warten.

    dh, wenn man Lock Lock Exit macht, besteht immer noch ein Lock.

    Du kannst innerhalb/außerhalb des Synclock auch mit Monitor.IsEntered(lockobject) prüfen, ob der Lock noch besteht
    Gut, das ist sehr hilfreich.

    Nun noch ein letztes, auch wenn schon etwas von ursprünglichen Thema abweichend.

    Gibt es eine Möglichkeit ein Lock zu setzen, ohne es in der gleichen Methode wieder zu lösen? Folgendes schwebt mir vor.

    Wir haben Objekte A und B, von der gleichen Klasse.

    Beide Objekte sind wiederum vom anderen abhängig. Wenn ich nun folgenden Methoden habe:

    VB.NET-Quellcode

    1. Public Sub Methode1()
    2. SyncLock lock
    3. 'Tu etwas, was länger dauert
    4. 'Tu nun etwas auf den abhängigen Objekten mit
    5. Methode2()
    6. End SyncLock
    7. End Sub
    8. Public Sub Methode2()
    9. SyncLock lock 'Hier auch wichtig!
    10. 'Tu hier irgendwas.
    11. End SyncLock
    12. End Sub


    So kann es passieren, dass ich ein klassisches DeadLock erhalte, wenn der zweite Thread Objekt B aufruft, während Thread A noch nicht Methode2() auf B aufgerufen hat.

    Ich möchte jetzt gleich in allen abhängigen Objekten die Locks mitsetzen, da dann der Thread 2 Methode1 auf B gar nicht erst aufrufen könnte. Ich würde demnach Methoden SetLock() und ReleaseLock() definieren, die das Lock auf dem aktuellen, wie auch auf allen abhängigen Objekten sperren, bzw. lösen. Besteht die Möglichkeit ohne unhübschen Workaround zu einer solchen Lösung zu kommen?

    Leider ist jedoch die Software so geschrieben, dass ich nicht einfach mein Lock-Objekt als Shared setzen kann, dann würde ich viel zu viel sperren. Ich habe statt dessen eine Liste aller Objekte, die von meinem derzeit betrachteten abhängen. Auf diese muss ich mich beschränken.

    Vielen Dank für Tips im Voraus!
    Du möchtest also 2 Objekte aus derselben Klasse erzeugen und diese sind abhängig voneinander ?
    Wie soll das Aussehen?
    Mir scheint da die Konzeption ein wenig wirr.
    Das ist meine Signatur und sie wird wunderbar sein!
    Hi,

    die Konzeption stammt nicht von mir. Ich bin gezwungen auf Vorhandenem aufzubauen.

    Aber ja, es ist tatsächlich so. Ein Objekt der Klasse besitzt eine Liste von Objekten derselben Klasse, die von dieser Klasse abhängig sind.

    Sehe ich es richtig, dass ich in der Klasse ein Mutex definieren kann, welches ich dann auf den Objekten aufrufe/prüfen kann?

    edit(18:06): Ich will jetzt folgende Lösung implementieren. Ob und wie der hier beschriebene Fall eintreten kann sei zuerst dahingestellt:

    • Alle Objekte sind von der gleichen Klasse.

    • Es gibt Objekt A, B und C

    • Folgende Abhängigkeiten bestehen.

    • A<-B<-C also ist B von A abhängig.


    Betrachten wir die Funktion GetMutex()

    Folgende Schritte werden abgearbeitet:

    Wenn kein Mutex existiert, erschaffe ein neues. Sonst warte bis das Mutex frei ist.

    Wenn frei: Gehe über alle abhängigen Objekte und Rufe dort GetMutex() auf.

    Problem:
    Schlussendlich landen wir wieder bei A!

    Hier soll jetzt natürlich die Kette abbrechen, denn A ist ja schon durch den Thread geschützt. Über Mutex.OpenExisting kann ich prüfen, ob schon ein Mutex mit einem speziellen Namen existiert. Wenn nicht, wird eine Exception geworfen.

    Ich muss also beim Setzen des Mutex einen Namen mit angeben. Dieser muss vom Thread und vom Objekt abhängig sein.

    Frage

    Gibt es eine Möglichkeit einen eindeutigen Namen zu jedem Objekt zu erzeugen? Würde hier Object.GetHashCode genügen? Oder kann ich irgendwie direkt prüfen, ob mein aktueller Thread das vorliegende Mutex besitzt?

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