Prozesssynchronisation mit Producer-Consumer-Pattern

  • Java

Es gibt 11 Antworten in diesem Thema. Der letzte Beitrag () ist von Trade.

    Prozesssynchronisation mit Producer-Consumer-Pattern

    Moin,

    wir behandeln in Informatik gerade das Thema Nebenläufigkeit und Prozesssynchronisation.
    An sich kein Problem: Es gibt einen kritischen Abschnitt, der synchronisiert werden muss, damit der Zugriff auf die Ressourcen nicht gleichzeitig abläuft. Dazu gibt es in Java ja das synchronized-Schlüsselwort, um diese Abschnitte zu definieren. Damit konnten wir das dann regeln, dass der Zugriff über Monitore abläuft und es zu keinen Komplikationen kommt.

    Jetzt haben wir das Erzeuger-Verbraucher-Problem behandelt und ich blicke da irgendwie nicht ganz durch, auch was die entsprechenden Methoden in Java angeht.
    Entsprechend folgendes Szenario:
    • Wir haben einen Speicher, der eine Kiste beinhaltet
    • Der Erzeuger produziert Kisten und legt diese auf dem Speicher ab
    • Der Verbraucher holt diese ab und kann dann was mit diesen anstellen
    Soweit ganz logisch. Das Problem ist ja jetzt, dass es passieren kann, dass die beiden unterschiedlich schnell arbeiten und somit der Erzeuger schon bspw. Kiste 4 produziert hat, obwohl der Verbraucher gerade noch dabei ist, Kiste 2 einzulagern und Kiste 3 somit noch gar nicht abgeholt hat. Somit ist der Speicher voll und der Erzeuger kann die Kiste nicht ablegen.
    Was ich jetzt nicht verstehe: Wir haben dann die Methoden object.wait() und object.notify() verwendet, damit die zwei Threads (Erzeuger u. Verbraucher) jeweils warten, bis der Platz frei (bzw. voll) ist und dann erst ihre Aktion ausführen.

    So sieht das ungefähr aus:

    Speicher

    Java-Quellcode

    1. public class SPEICHER{
    2. private KISTE kiste;
    3. private Boolean frei;
    4. public SPEICHER(){
    5. frei = true;
    6. kiste = null;
    7. }
    8. public synchronized void Ablegen(KISTE k)
    9. {
    10. while(!frei){
    11. try{
    12. wait();
    13. }
    14. catch(Exception e){
    15. }
    16. }
    17. frei = false;
    18. kiste = k;
    19. System.out.println("Kiste Nr. " +kiste.NummerGeben()+ " abgelegt");
    20. notify();
    21. }
    22. public synchronized KISTE Holen()
    23. {
    24. KISTE k;
    25. while(frei){
    26. try{
    27. wait();
    28. }
    29. catch(Exception e){
    30. }
    31. }
    32. frei = true;
    33. k=kiste;
    34. kiste = null;
    35. System.out.println("Kiste Nr. " +k.NummerGeben()+" abgeholt");
    36. notify();
    37. return k;
    38. }
    39. }



    Erzeuger

    Java-Quellcode

    1. public class ERZEUGER extends Thread {
    2. private SPEICHER speicher;
    3. private int zeit;
    4. private int kNummer;
    5. public ERZEUGER(SPEICHER s, int zeitNeu) {
    6. zeit = zeitNeu;
    7. speicher = s;
    8. kNummer = 0;
    9. }
    10. public void run() {
    11. while (true) {
    12. speicher.Ablegen(Produzieren());
    13. }
    14. }
    15. KISTE Produzieren() {
    16. long akt, ende;
    17. akt = System.currentTimeMillis();
    18. ende = akt + zeit;
    19. while (akt < ende) {
    20. try {
    21. wait(ende - akt);
    22. } catch (Exception e) {}
    23. akt = System.currentTimeMillis();
    24. }
    25. kNummer++;
    26. System.out.println("Kiste Nummer " + kNummer + " wurde produziert.");
    27. return (new KISTE(kNummer));
    28. }
    29. }



    Verbrauch

    Java-Quellcode

    1. public class VERBRAUCHER extends Thread {
    2. private SPEICHER speicher;
    3. private int zeit;
    4. private Random zufall
    5. public VERBRAUCHER(SPEICHER s, int zeitNeu) {
    6. zeit = zeitNeu;
    7. speicher = s;
    8. zufall = new Random();
    9. }
    10. public void run() {
    11. while (true) {
    12. Einlagern(speicher.Holen());
    13. }
    14. }
    15. void Einlagern(KISTE kiste) {
    16. long akt, ende;
    17. akt = System.currentTimeMillis();
    18. ende = akt + zeit;
    19. while (akt < ende) {
    20. try {
    21. wait(ende - akt);
    22. } catch (Exception e) {}
    23. akt = System.currentTimeMillis();
    24. }
    25. System.out.println("Kiste Nummer " + kiste.NummerGeben() + " wurde eingelagert.");
    26. }
    27. }



    Ja, ich weiß, der Code ist ranzig, aber darum soll es jetzt nicht gehen. Die Kiste ist nur eine Datenstruktur (Klasse), die eine Nummer enthält, also recht simpel.
    Jetzt die Fragen:

    Im Speicher sind die Methoden Ablegen und Holen als Monitor erklärt, sodass der Zugriff auf diese jeweils nur von einem Objekt aus stattfinden sollte. Warum also haben wir dann die wait() und notify()-Methoden an der Stelle im Erzeuger und Vebraucher benötigt? Nach meinem Verständnis müsste doch zuerst der Erzeuger den Monitor für sich sperren, sodass der Verbraucher erstmal nichts abholen kann, weil es nicht darf. Was genau macht der dann? Setzt er sich in eine Warteschlange für den Monitor, bis er intern dann den Zugriff bekommt? Sobald die Kiste dann abgelegt ist, wird der Monitor ja freigegeben und dann dürfte der Verbraucher die Kiste abholen. Zugleich könnte der Erzeuger dann nichts ablegen.
    Das müsste doch als Synchronisation reichen, oder? Wozu dann noch mit einer while-Schleife pollen und solange warten, bis er das frei mit dem entsprechenden Wert hat? Kann es sein, dass ich falsch verstehe, was synchronized macht? Imo müsste das ja bereits reichen, damit es nicht zu diesem Szenario (Erzeuger und Verbraucher wollen gleichzeitig zugreifen) und folglich zu einer NullPointerException kommt, oder? Selbst wenn diese unterschiedlich schnell arbeiten, wartet halt der andere durch das synchronized entsprechend länger, bis er den Monitor bekommt.
    Warum genau existiert dann das Erzeuger-Verbraucher-Problem?

    Außerdem seht Ihr z. B. im Verbraucher bei Zeile 24 das wait. Ich dachte, das kann man eig. nur in Verbindung mit synchronized verwenden? Was macht denn das da? Ist das sowas wie Thread.Sleep() in .NET? Warum genau existiert denn in Java jetzt wait, notify und notifyAll. Ich sehe da den Sinn irgendwie nicht.
    Ich hab's dann mal ausprobiert, das wegzulassen und es kam wirklich zu einer NullPointerException. synchronized alleine reicht dann anscheinend nicht. Aber warum?
    Ich danke euch für Antworten.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Trade“ ()

    Ne, das haut alles hin. Ich hätte die Methode natürlich eher als ​throws gekennzeichnet, aber weißte ja... Schule hat ihre Gründe.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Hi.

    Deine Klasse Speicher stellt einen Mutex dar. Seine Operationen Ablegen() und Holen() sind atomar, da sie als synchronized markiert sind. Intern wird ein Monitor benutzt, das stimmt. Wichtig ist: Ein Mutex kann nur einen (!) Speicherplatz verwalten. Im Speicher kann also immer nur eine Kiste liegen. Es ist also nicht möglich, dass der Erzeuger mehr als eine Kiste im Voraus produziert. Eine weitere wichtige Info zum Verständnis ist: Beim Aufruf von wait() verlässt der Thread den Monitor in einer atomaren Operation. Wird ein Thread per notify() aufgeweckt, erhält er den Monitor in einer atomaren Operation zurück.

    Angenommen, der Verbraucher startet zuerst. Er ruft Holen() auf und rennt in das wait(), weil frei == true gilt. Gleichzeitig verlässt der Thread den Monitor und wird aus der Liste rechenbereiter Threads entfernt, weil er ja blockiert ist.

    Irgendwann rennt der Erzeuger los. Er produziert eine Kiste und ruft Ablegen() auf. Das kann er, weil der Monitor vom blockierten Thread freigegeben wurde. Er legt die Kiste ab, denn es gilt frei == true. Der folgende Aufruf an notify() signalisiert einem wartenden Thread, dass er wieder rechnen darf. Es dauert aber noch, bis er vom System auch wirklich aufgerufen wird. Auf einem Multicore-System könnte er zwar sofort drankommen, aber das geht nicht, weil der Erzeuger den Monitor immer noch besitzt. Nach der Rückkehr der Methode Ablegen() ist der Monitor frei.

    Der Verbraucher-Thread darf jetzt fortfahren. Er bekommt den Monitor und dreht eine weitere Runde in der While-Schleife, wobei er aber free == false vorfindet und daher nicht erneut wartet. Er konsumiert die Kiste. Das folgende notify() bleibt ohne Wirkung, weil kein anderer Thread blockiert ist.

    Es gibt noch zwei andere Varianten: 1: Der Erzeuger startet und ist exakt gleich schnell wie der Verbraucher (langweilig). 2: Der Erzeuger startet zuerst und ist schneller als der Verbraucher. Der Ablauf ist wie oben, nur dass die Warteoperationen umgekehrt sind.

    Die wait()-Aufrufe (d.H. die Zeilen 19-27 in Erzeuger UND Verbraucher) können m.E. weg - sie dienen vermutlich nur dazu, dass alle Fälle mal auftreten. Beim Weglassen dürfte eigentlich keine NullPointerException kommen (muss ich mal testen), denn die Synchronisation findet in der Speicher-Klasse statt.

    Das Erzeuger-Verbraucher-Problem wurde nicht von irgendwem theoretisch konstruiert (ok, doch), sondern es existiert auch in der Realität. Folgende Vorstellung: Ein Heuboden, 2 Arbeiter, nur eine Leiter. Einer muss frisches Heu hochschaffen, der andere altes runternehmen. Der Boden hat eine begrenzte Größe. Natürlich können nicht beide gleichzeitig auf der Leiter stehen. Außerdem darf der Boden nicht zusammenbrechen, wenn der, der Heu runternehmen soll, zu langsam ist oder nicht häufig genug die Leiter hochkommt. Einschränkung durch die Rahmenbedingungen: Der Boden kann nie leer werden.

    In der Informatik erzeugt die Lösung dieses Problems große Performance-Vorteile: Üblicherweise ist das Produzieren und Konsumieren von Items sehr viel teurer als die Warteschlangenverwaltung (und sehr viel teurer als ein "new KISTE", z.B. ein Datenbankabruf, der Zeit braucht). Natürlich soll dabei das verarbeitende Programm nicht blockiert werden. Es läuft daher in einem separaten Thread, was aber zu Problemen führt, wenn beide parallel auf die Queue zugreifen.
    Gruß
    hal2000

    hal2000 schrieb:

    Im Speicher kann also immer nur eine Kiste liegen.
    Meinst Du den Arbeitsspeicher oder die Speicher-Klasse? ^^
    Warum genau ist das so?

    hal2000 schrieb:

    Beim Aufruf von wait() verlässt der Thread den Monitor in einer atomaren Operation.

    hal2000 schrieb:

    Das kann er, weil der Monitor vom blockierten Thread freigegeben wurde.
    Ahh, das macht dann schon mal mehr Sinn, danke. Heißt also, wenn der Verbraucher jetzt schneller ist, dann würde er ohne das ​wait() einfach durchrennen oder andernfalls den Monitor blockieren, sodass der Erzeuger keine Kiste im Speicher ablegen könnte. Das ist also dazu da, dass er abwartet, bis er entsprechend darf und solange gibt er den Monitor für den Erzeuger frei, damit das hinhaut. Und sobald der dann fertig ist, setzt er ​frei = false und benachrichtigt den wartenden Verbraucher-Thread, sodass dieser wieder arbeitet und im Endeffekt die Kiste abholt.

    hal2000 schrieb:

    Die wait()-Aufrufe (d.H. die Zeilen 19-27 in Erzeuger UND Verbraucher) können m.E. weg - sie dienen vermutlich nur dazu, dass alle Fälle mal auftreten.

    Zeilen 19-27 sind glaube ich dazu da, dass man halt das zeitlich absichtlich abtrennen kann, also jo, damit man alle Szenarien nachbilden kann. Mich hatte das nur gewundert, weil ich dachte ​wait kann man nur in ​synchronized aufrufen. Aber von was wird das dann eig. wieder aufgeweckt? Weil das soll ja eig. autonom nur warten, unabhängig davon, was der Erzeuger macht.

    hal2000 schrieb:

    Beim Weglassen dürfte eigentlich keine NullPointerException kommen
    Wenn man die ​wait-Aufrufe weghaut nicht, nein. Aber ich meinte, wenn man das komplette Warten und Benachrichtigen im Speicher rauslässt. Dann kam es bei uns zu einer Exception. Das lag halt daran, dass er dann wahrscheinlich versucht hat, was aus dem leeren Speicher zu holen.

    Also wir halten fest: Das Problem existiert, da die beiden unterschiedlich schnell arbeiten können und dann ist es nicht mehr so einfach, beide zu synchronisieren. Darum muss man darauf aufbauen, dass die Threads mehr oder weniger kommunizieren könne.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Danke @petaod. Wir lernen halt zur Zeit die Mechanismen und da veranschaulichen wir uns das entsprechend mit solchen Projekten, wie das funktioniert. Dass .NET hier bereits Lösungen bereitstellt, wusste ich bereits (ConcurrentBag<T>, ConcurrentQueue<T>, ConcurrentStack<T>, ...) und die nimmt man dann halt her. Ich würde im normalen Leben auch niemals Java verwenden, wenn's nicht sein muss.^^

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

    Trade schrieb:

    Meinst Du den Arbeitsspeicher oder die Speicher-Klasse?

    Mit "Speicher" meinte ich die Klasse SPEICHER im ersten Spoiler oben.

    Trade schrieb:

    Warum genau ist das so?

    Er kann nur eine Kiste aufnehmen, weil er nur zwischen frei = true/false unterscheiden kann - die Haupteigenschaft des Mutex. Der Mutex ist ein Spezialfall eines sogenannten Semaphors, das genauso funktioniert, mit Hilfe eines Zählers aber mehrere Prozesse gleichzeitig tracken (hier: blockieren) kann. Genau genommen ist die 1-Kisten-Variante auch nur ein Spezialfall des Erzeuger-Verbraucher-Problems, weil eben nur eine Kiste möglich ist. Der allgemeine Fall erlaubt zum einen mehr als eine Kiste im SPEICHER und zum anderen beliebige, auch asymmetrische Anzahlen von Erzeugern und Verbrauchern.

    Trade schrieb:

    wenn der Verbraucher jetzt schneller ist, dann würde er ohne das wait() einfach durchrennen oder andernfalls den Monitor blockieren, sodass der Erzeuger keine Kiste im Speicher ablegen könnte

    Wenn das wait() im Verbraucher fehlt, kommt es darauf an, ob der Erzeuger vorher einmal gelaufen ist oder nicht. Wenn ja, verarbeitet er die eine Kiste ohne Fehler, denn er würde eh nicht warten. Danach haben wir denselben Fall, als wäre der Erzeuger noch nicht gelaufen: Es gilt frei = true und der Verbraucher hat den Monitor. Den behält er aber unendlich lange, da der Erzeuger den Monitor nicht bekommt --> Deadlock.

    Trade schrieb:

    Aber von was wird das dann eig. wieder aufgeweckt? Weil das soll ja eig. autonom nur warten, unabhängig davon, was der Erzeuger macht.

    Gute Frage - die Doku sagt dazu (Hervorhebungen von mir):

    public final void wait(long timeout) throws InterruptedException
    Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.

    Es geht also tatsächlich nur um die Zeit, da die enthaltende Methode nicht Teil des objekteigenen Monitors ist (kein synchronized!). Interessant, dass das trotzdem funktioniert. Die Doku sagt übrigens auch:

    Synchronization is built around an internal entity known as the intrinsic lock or monitor lock.

    Soll heißen: synchronized an einer Methode bedeutet nichts anderes als:

    C#-Quellcode

    1. void foo() {
    2. synchronized(this) { // enter monitor
    3. // code
    4. } // exit monitor
    5. }

    Das Ganze interferiert nicht mit dem SPEICHER, weil Erzeuger und Verbraucher zwei unabhängige Objekte sind. Die können sich selbst locken, so viel sie wollen, denn ihre Threads sind alleine.

    Trade schrieb:

    wenn man das komplette Warten und Benachrichtigen im Speicher rauslässt. Dann kam es bei uns zu einer Exception. Das lag halt daran, dass er dann wahrscheinlich versucht hat, was aus dem leeren Speicher zu holen.

    Wenn auch die Schleife fehlt, dann ja. Der Verbraucher war schneller und hat nicht gewartet, bis der Erzeuger den Slot gefüllt hatte --> Fail.

    Trade schrieb:

    dass die Threads mehr oder weniger kommunizieren könne

    Und genau in diese Kategorie fällt auch dieses ganze Thema: Interprozesskommunikation. Neben dem Mutex, der hier behandelt wird (ok, hier wird mit nem Monitor gecheatet, aber für den Schulbetrieb soll das mal erlaubt sein), gibts dann noch die erwähnten Semaphoren und Message Passing. Zu letzterem zählt dann sowas wie RPC, MPI, DCOM und wie sie alle heißen.
    Gruß
    hal2000

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

    hal2000 schrieb:

    Der allgemeine Fall erlaubt zum einen mehr als eine Kiste im SPEICHER und zum anderen beliebige, auch asymmetrische Anzahlen von Erzeugern und Verbrauchern.
    Ja, da hat unser Lehrer gesagt, dass wir das die kommende Woche machen. :)

    hal2000 schrieb:

    Es geht also tatsächlich nur um die Zeit, da die enthaltende Methode nicht Teil des objekteigenen Monitors ist
    Ah okay, das macht dann Sinn, danke. Doku lesen hilft halt manchmal schon viel. :D

    hal2000 schrieb:

    Der Verbraucher war schneller und hat nicht gewartet, bis der Erzeuger den Slot gefüllt hatte --> Fail.
    Genau.

    hal2000 schrieb:

    gibts dann noch die erwähnten Semaphoren und Message Passing. Zu letzterem zählt dann sowas wie RPC, MPI, DCOM und wie sie alle heißen.
    Semaphoren haben wir schon durchgenommen. Afaik nutzen Monitore ja intern auch Semaphoren. Da haben wir halt das mit Prolaag und Verhoog nach Dijkstra besprochen und dass es eben so einen Zähler gibt, der angibt, wie viele Threads die Ressource haben dürfen. Ein Monitor nutzt dann wohl intern eine Semaphore mit 'nem Counter von 1, oder?
    Message Passing habe ich noch nie gehört. :P

    hal2000 schrieb:

    Soll heißen: synchronized an einer Methode bedeutet nichts anderes als: [...]
    Jo, das hatte ich mir auch gedacht. Dass das unabhängig vom Speicher ist, macht dann Sinn.

    Also im Wesentlichen geht es halt darum, dass die Daten, die man teilt und deren Zugriff einen kritischen Abschnitt darstellt, von den einzelnen Threads kommen und man gegenseitig warten muss, bis diese adäquat vorhanden sind. Daher dieses Warten-Benachrichtigen-Prinzip, da die einfache Synchronisation daher nicht ausreicht.
    Wie würde man sowas in .NET umsetzen? Schon mit einem ManualResetEvent bzw. AutoResetEvent, oder? Also generell mit WaitHandles.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

    Trade schrieb:

    Afaik nutzen Monitore ja intern auch Semaphoren.

    Können sie, müssen sie aber nicht. Monitore basieren auf dem Konzept der Zustandsvariablen. Wie der Zugriff auf diese intern synchronisiert wird ist eine andere Frage. Kann ein Semaphor machen, muss aber nicht. Die Synchronisation ist sowieso vom System abhängig, weil die CPU den Lock-Mechanismus hardwareseitig unterstützen muss. Ohne Hardware-Support ist das Ganze ineffizient, weil busy waiting vonnöten ist; siehe en.wikipedia.org/wiki/Test-and-set.

    Trade schrieb:

    Prolaag und Verhoog nach Dijkstra

    ...sind das Grundprinzip eines Semaphors.

    Trade schrieb:

    Ein Monitor nutzt dann wohl intern eine Semaphore mit 'nem Counter von 1, oder?

    Ein Semaphor mit MaxCount = 1 nennt man Mutex. Der Monitor ist nicht dasselbe, siehe oben. Er benutzt dieses Konzept nur. Siehe auch en.wikipedia.org/wiki/Monitor_(synchronization) - im Abschnitt "condition variable" steht direkt als erster Satz das, was du schon festgestellt hast:

    For many applications, mutual exclusion is not enough.

    --> "synchronized" alleine reicht nicht aus, man braucht auch wait() / notify().

    Trade schrieb:

    Message Passing

    ...ist einfach ein anderer Synchronisationsmechanismus. Der Unterschied liegt in der Art der Speicherorganisation: Shared vs. Distributed Memory (oder anders: Mehrere CPUs / Kerne auf einem Board vs. mehrere Rechner über Netzwerk verbunden). Aber das geht schon in Richtung verteiltes Rechnen :)

    Trade schrieb:

    Also im Wesentlichen geht es halt darum, dass die Daten, die man teilt und deren Zugriff einen kritischen Abschnitt darstellt, von den einzelnen Threads kommen und man gegenseitig warten muss, bis diese adäquat vorhanden sind.

    Korrekt. Das Problem ist, dass eine Anweisung wie "x = x + 1" mehr als eine Rechenoperation ist (hier: x lesen, 1 addieren, x überschreiben). Zwischen jeder Teiloperation kann der Scheduler dazwischenfunken, wobei ein anderer Thread x ggf. verändert. Die Synchronisation verhindert genau das, indem sie die 3 Operationen unteilbar zusammenfasst.

    Trade schrieb:

    Also generell mit WaitHandles

    In .NET musst du dich um das Lock-Target und die Events selbst kümmern. Das Äquivalent zum Java-Code wäre:

    VB.NET-Quellcode

    1. Dim a As AutoResetEvent
    2. SyncLock Me ' synchronized {
    3. a.WaitOne() ' wait()
    4. a.Release() ' notify()
    5. End SyncLock ' }

    SyncLock object ... End SyncLock ist äquivalent zu Monitor.Enter(object) ... Monitor.Exit(object)
    Gruß
    hal2000

    hal2000 schrieb:

    ...sind das Grundprinzip eines Semaphors.
    Jo, schon klar.^^

    hal2000 schrieb:

    Das Problem ist, dass eine Anweisung wie "x = x + 1" mehr als eine Rechenoperation ist (hier: x lesen, 1 addieren, x überschreiben)
    Ja, das Beispiel hatten wir auch entsprechend gemacht. Im speziellen Anwendungsfall des Einzahlens und Abhebens von Geld auf ein Konto.

    Danke auch für die Übersetzung in VB.NET. Dann war das so, wie ich das mir gedacht habe.
    Nur sollte man afaik nie this bzw. Me locken, insofern das öffentlich modifiziert ist. Stand zumindest so auf MSDN. Stattdessen halt eine private, globale Variable anlegen.
    Was es ja auch noch gibt, ist das ​MethodImplAttribute (heißt afaik so). Bloß würde ich da lieber direkt einen ​lock um den ganzen Inhalt der Methode setzen. Aber gut, je nachdem, was einem besser gefällt.

    Du hast mir sehr weitergeholfen, das Problem zu verstehen. Jetzt ist alles um einiges klarer, danke Dir vielmals!

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!: