Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

Von einem Thread auf einen anderen zugreifen [und captured variables]
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo tscherno, hallo zusammen,

die Verwendung von anonymen Delegaten zur Parameterübergabe an Threads kann, wenn man nicht genau aufpasst, fehlerhafte Ergebnisse produzieren und ist daher nur mit Einschränkungen empfehlen!

In der Doku (Abschnitt "Anonyme Methoden (C#-Programmierhandbuch)") wird zwar die Verwendung von anonymen Delegaten im Zusammenhang mit dem Starten von Threads als gutes Beispiel hingestellt:
Zitat
Beispielsweise kann die Angabe eines Codeblocks anstelle eines Delegaten in einer Situation nützlich sein, in der das Erstellen einer Methode als unnötiger Aufwand erscheinen würde. Ein gutes Beispiel dafür wäre das Starten eines neuen Threads.
Aber das gilt nur, wenn keine Parameterübergabe in dem von dir, tscherno, vorgeschlagenen Sinne verwendet wird. Das Beispiel in der Doku verwendet denn auch nur Konstanten innerhalb des anonymen Delegaten.

Dass die Parameterübergabe schiefgehen kann/wird, zeigt folgendes Beispiel sehr deutlich:


using System;
using System.Threading;

static class App
{
   public static void Main (string [] astrArg)
   {
      ThreadStart starter;
      for (int i = 1; i < 10; ++i) {
         starter = delegate () {
            Thread.Sleep (100); // Simuliert eine Verzögerung beim Thread-Wechsel.
            M (i);
         };
         new Thread (starter).Start ();
         Thread.Sleep (30); // Simuliert den Aufwand für weiter Operationen in der Schleife
      }
   }

   public static void M (int i)
   {
      Thread.Sleep (200); // Simuliert den Aufwand für weiter Operationen vor der Ausgabe
      Console.WriteLine (i);
   }
}

Die Ausgabe ist
4
5
6
7
8
9
10
10
10
und nicht wie erwartet 1 .. 9. Wichtig ist, dass diese Fehlverhalten auch ohne das Thread.Sleep (100) in dem anonymen Delegaten auftreten kann, weil es jederzeit zu Verzögerungen beim Threadwechsel kommen kann. Das Thread.Sleep simuliert einfach eine solche Verzögerung, die jederzeit auch ohne das Thread.Sleep auftreten kann.

Der springende Punkt, weshalb die Parameterübergabe nicht funktioniert ist, dass die Variable i zwar tatsächlich in dem Moment der Erzeugung (und Zuweisung) des Delegaten-Objekts (starter = delegate ...) gespeichert wird, wie man das erwarten würde und wie das auch in der Doku steht. Wird aber die Variable i später geändert (hier in der Reinitialisierung der for-Schleife), wird auch der im Delegaten-Objekt gespeicherte Wert aktualisiert. Ich habe das untersucht: Es wird vom Compiler extra Code generiert, der diese Aktualisierung vornimmt.

Die Aktualisierung ist also wohl Absicht, obwohl mir schleierhaft ist, warum.

Editiert: Jedenfalls muss man das folgende Fazit ziehen: Es ist potentiell gefährlich, Parameter per anonymen Delegaten an Threads zu übergeben und, wenn man nicht genau weiß, was man tut, man sollte daher eher die Finger davon lassen.

Es bleibt also der übliche Weg, ParameterizedThreadStart zu verwenden und ein Object-Array, das alle Parameter enthält, an Thread.Start zu übergeben.

herbivore

PS: Das Verhalten hat sich in C# 5.0 zumindest in Bezug auf foreach-Schleifenvariablen geändert. Wird eine solche Schleifenvariable als captures variable verwendet, dann wird jetzt der Wert aus dem jeweiligen Schleifendurchlauf gecaptured. Eine foreach-Schleifenvariable gilt jetzt im Schleifenrumpf deklariert, siehe Has foreach's use of variables been changed in C# 5? und What else is new in C# 5?, Abschnitt "Using loop variables in lambdas".

Suchhilfe: 1000 Worte, captures, captured variables, closures, delegate, delegates, Delegaten, lambda expressions, Lambda-Ausdrücke.
private Nachricht | Beiträge des Benutzers
svenson
myCSharp.de - Member



Dabei seit:
Beiträge: 8775
Herkunft: Berlin

beantworten | zitieren | melden

Zitat von herbivore
die Verwendung von anonymen Delegaten zur Parameterübergabe an Threads ist nicht zu empfehlen und kann fehlerhafte Ergebnisse produzieren!

Kann, muss aber nicht. Teste mal diesen Code:

      for (int  i = 1 ; i < 10 ; ++i )
        {
            int a = i;
            starter = delegate( )
            {
                Thread.Sleep( 100 ); // Simuliert eine Verzögerung beim Thread-Wechsel.
                M( a );
            };
            new Thread( starter ).Start( );
            Thread.Sleep( 30 ); // Simuliert den Aufwand für weiter Operationen in der Schleife
        }

Hier wird die Schleifenvariable i an die Variable a zugewiesen. Der Output ist dann wie erwartet.

Man nennt dieses Verhalten von anonymoen Delegaten "Capturing", bzw. die Variablen werden als "captured variables" bezeichnet.

http://www.yoda.arachsys.com/csharp/csharp2/delegates.html

Eben diese Variablen sind gefährlich und sollten nicht bzw. nur mit Bedacht in anonyme Delegaten gesteckt werden (da braucht man nichtmal Threads um komisches Verhalten zu produzieren). Kopiert man die Werte (bei Referenzen gibt es eh Probleme) kurz vor dem Aufruf um (Deklaration muss im Scope des Delegaten erfolgen!), dann ist das ganze thread-safe.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von svenson am .
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo svenson,

interessant! Dass es hier um captured variables geht, war schon klar. Aber dass es hilft den Scope einzuschränken, hatte ich nicht bedacht. Im nachhinein ist es jedoch klar.

Wobei mir immer noch nicht klar ist, warum captured variables nicht so implementiert sind, dass sie nur einmalig bei ihrer Erzeugung aufzeichnet werden (also in dem Moment, in dem das Delegaten-Objekt erzeugt wird). Denn alleine der Name captured variables legt implizit nahe, dass die Aufzeichnung einmalig bei der Erzeugung der jeweiligen Variablen erfolgt. In Wirklichkeit wird eine captured variable aber bei jeder Änderung der (zugehörigen Instanz der) Urspungsvariablen aktualisiert.

herbivore
private Nachricht | Beiträge des Benutzers
svenson
myCSharp.de - Member



Dabei seit:
Beiträge: 8775
Herkunft: Berlin

beantworten | zitieren | melden

Man muss halt in Expressions denken, nicht in Variablen. Und dahin zielt das Ganze ja. Und der Kontext einer Expression ist halt der Scope des Delegaten.

    static SomeAction MakeDelegate()
    {
        Random rng = new Random();
        
        return delegate { Console.WriteLine (rng.Next()); };
    }

Unter LINQ wird noch deutlicher, welch lustige Sachen man damit machen kann. Ähnelt aber an die C-Wettbewerbe an undurchschaubarem Code.

Jon Skeet: Coding Blog: Fun with captured variables
Dieser Beitrag wurde 3 mal editiert, zum letzten Mal von svenson am .
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo svenson,

es heißt aber nun mal captured variables und nicht captured expressions. Insofern finde ich es schon sinnvoll in Variablen zu denken. "Denn alleine der Name captured variables legt implizit nahe, dass die Aufzeichnung einmalig bei der Erzeugung der jeweiligen Variablen erfolgt."

herbivore
private Nachricht | Beiträge des Benutzers
tscherno
myCSharp.de - Member

Avatar #avatar-2584.gif


Dabei seit:
Beiträge: 637
Herkunft: Nürnberger Land

beantworten | zitieren | melden

Hallo,

danke für euere Bemühunhen genaueres herauszufinden. Verstehe ich es richtig dass man diese Methode verwenden kann (nur verwenden soll) wenn die Übergabeparameter sich im Scope des Delegates befinden? Hab noch nicht viel Erfahrung mit solchen Delegates, deshalb hacke ich hier mal nach.

Gruss
tscherno
To understand recursion you must first understand recursion
-
http://www.ilja-neumann.com
C# Gruppe bei last.fm
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo tscherno,
Zitat
Verstehe ich es richtig dass man diese Methode verwenden kann (nur verwenden soll) wenn die Übergabeparameter sich im Scope des Delegates befinden?
nein, so kann man das nicht sagen. Man kann sagen, dass man anonyme Delegaten zur Übergabe von Parametern an Threads dann verwenden kann, wenn die lokalen Variablen, die man dafür verwendet, im Scope des Delegaten nicht geändert werden.

Wir reden dabei über lokale Wertvariablen und über lokale Refenrenzvariablen von Objekten, die immutable sind. Denn bei anderen Referenzvariablen und bei Instanzvariablen musst man unabhängig von der Art der Übergabe sowieso aufpassen.

herbivore
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo zusammen,

schon lustig, wo einem das hier besprochene in ganz anderem Zusammenhang und viel später wieder über den Weg läuft:

.NET Quiz BLog: .NET Rätsel #1 Tricky Lambda

herbivore
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo zusammen,

wie ich heute erfahren habe, hat sich das Verhalten von captured variables zumindest für foreach-Schleifenvariablen in C# 5.0 geändert. Ich habe diese neue Information direkt in dem betreffenden Beitrag in Von einem Thread auf einen anderen zugreifen [und captured variables] als PS ergänzt.

herbivore
private Nachricht | Beiträge des Benutzers