Advanced   Java   Services Datenübergabe an Threads Back Next Up Home


Auf welche Daten hat ein Thread Zugriff

Ein Thread hat natürlich Zugriff auf alle Daten, die in der gerufenen Methode angelegt werden. Darüber hinaus hat er Zugriff auf Daten, die in der Klasse liegen, in der die Methode liegt, wobei hier zu beachten ist, ob die Methode statisch oder nichtstatisch ist. des weiteren können Daten über den Parameter der Methode übergeben werden.


Daten über den Parameter vom Typ Object übergeben

In C# erhält der Threadkonstruktor ein Delegate auf eine Methode, die dann in einem eigenen Thread ausgeführt wird. Da dem Threadkonstruktor auch ein Delegate übergeben werden kann, das für eine Methode mit einem Parameter vom Typ Object steht, ist dies eine erste Möglichkeit Daten an einen Thread zu übergeben.


Beispiel

Es soll ein StringBuilder verwendet werden, der von zwei Threads gefüllt wird. Da der Parameter von Methoden die dem Threadkonstruktor übergeben werden immer vom Typ Object ist, stellt das kein Problem dar.

private static void AppendChar(Object sb)
{
   if (sb is StringBuilder)
   {
      for (int i = 0; i < 10; i++)
      {
         ((StringBuilder)sb).Append((char)('a' + i));
         Thread.Sleep(new TimeSpan(1));
      }
   }
}
private static void AppendDigit(Object sb)
{
   if (sb is StringBuilder)
   {
      for (int i = 0; i < 10; i++)
      {
         ((StringBuilder)sb).Append((char)('0' + i));
         Thread.Sleep(new TimeSpan(1));  // 1 tick = 100 Nanosekunden
      }
   }
}
private static void Main(string[] args)
{
   StringBuilder sb = new StringBuilder();

   Thread th1 = new Thread(AppendChar);
   Thread th2 = new Thread(AppendDigit);
   th1.Start(sb);
   th2.Start(sb);

   th1.Join();
   th2.Join();
   Console.WriteLine(sb);
}

Da die beiden Threads dasselbe StringBuilderobjekt bearbeiten fallen die Ergebnisse recht unterschiedlich aus.
Hier drei Abläufe.

csharp-threads-28.jpg

csharp-threads-29.jpg

csharp-threads-30.jpg


Datenübergabe über den Konstruktor

Genauer gesagt: Datenübergabe über den Konstruktor der Klasse, in der die zu rufende Methode liegt. Die Methode greift also auf Daten der Klasse zu, in der sie liegt.


Beispiel

Der folgenden Klasse werden über den Konstruktor Daten übergeben, die von der Methode DoWork() manipuliert werden.

class Worker
{
   private StringBuilder sb;
   private char ch;

   public Worker(StringBuilder sb, char ch)
   {
      this.sb = sb;
      this.ch = ch;
   }

   public void DoWork()
   {
      for (int i = 0; i < 15; i++)
      {
         sb.Append(ch);
         Thread.Sleep(new TimeSpan(1));
      }
   }
}

Zwei Threads greifen auf diese Methode zu.

private static void Main(string[] args)
{
   StringBuilder sb = new StringBuilder();
   Worker w1 = new Worker(sb, '1');
   Worker w2 = new Worker(sb, '2');

   Thread th1 = new Thread(w1.DoWork);
   Thread th2 = new Thread(w2.DoWork);
   th1.Start();
   th2.Start();

   th1.Join();
   th2.Join();
   Console.WriteLine(sb);
}

Durch die kleine Pause erhöht sich die Wahrscheinlichkeit, daß die Threads den StringBuilder nicht exklusiv erhalten.

Drei Ausgaben

csharp-threads-31.jpg

csharp-threads-32.jpg

csharp-threads-33.jpg


Datenübergabe durch Verwendung von Lambda-Ausdrücken

Das erste Beispiel wird nun mit Hilfe von Lambda-Ausdrücken geschrieben.

private static void beispiel02()
{
   StringBuilder sb = new StringBuilder();

   Thread th1 = new Thread((Object sb) =>
   {
      if (sb is StringBuilder)
      {
         for (int i = 0; i < 10; i++)
         {
            ((StringBuilder)ob).Append((char)('a' + i));
            Thread.Sleep(new TimeSpan(1));
         }
      }
   });

   Thread th2 = new Thread((Object sb) =>
   {
      if (sb is StringBuilder)
      {
         for (int i = 0; i < 10; i++)
         {
            ((StringBuilder)ob).Append((char)('0' + i));
            Thread.Sleep(new TimeSpan(1));
         }
      }
   });

   th1.Start(sb);
   th2.Start(sb);

   th1.Join();
   th2.Join();
   Console.WriteLine(sb);
}

Vereinfachte Datenübergabe durch Verwendung von Closures

Durch die Verwendung eines Lambdaausdrucks kann man den obigen Code noch deutlich vereinfachen. Der Lambdarumpf kann nämlich direkt auf Variablen der einhüllenden Methode zugreifen. Ein Lambdaausdruck, der auf Variablen der ihn einhüllenden Methode zugreift wird auch Closure genannt.

private static void Main(string[] args)
{
   StringBuilder sb = new StringBuilder();

   Thread th1 = new Thread(() =>
      {
         for (int i = 0; i < 10; i++)
         {
            sb.Append((char)('a' + i));
            Thread.Sleep(new TimeSpan(1));
         }
      });

   Thread th2 = new Thread(() =>
      {
         for (int i = 0; i < 10; i++)
         {
            sb.Append((char)('0' + i));
            Thread.Sleep(new TimeSpan(1));
         }
      });

   th1.Start();
   th2.Start();

   th1.Join();
   th2.Join();
   Console.WriteLine(sb);
}

Das folgende einfachere Beispiel zeigt noch einmal das Vorgehen

private static void Main(string[] args)
{
   int a = 12, b = 13, c = 14;

   Thread thread = new Thread( () =>
      {
         SomeMethod(a, b, c);
      });
   thread.Start();
}


public static void SomeMethod(int a, int b, int c)
{
   Console.WriteLine("{0} {1} {2}", a, b, c);
}

Fallen bei der Verwendung von Closures

Beispiel 1

Die Brüder Albahari bringen in ihrem Buch C# in a Nutshell (4.Ausgabe, S. 794) zur Warnung das folgende Beispiel

private static void Albahari01()
{
   for(int i=0; i<10 ; i++)
   {
      Thread th = new Thread(() => Console.Write(i) );
      th.Start();
   }
}

Hier werden 10 Threads gestartet, die auf die Laufvariable einer Schleife zugreifen. Es ist nicht vorhersehbar, wann die einzelnen Threads auf die Laufvariable zugreifen.

Das Ergebnis kann durchaus mal so aussehen

csharp-threads-34.jpg

aber auch so

csharp-threads-35.jpg

oder so

csharp-threads-36.jpg


Beispiel 2

Als Lösung bietet das Buch an, die Threads auf eine temporäre Variable im Schleifenrumpf zuzugreifen, die die Laufvariable zwischenzuspeichern.

private static void Albahari02()
{
   for(int i=0; i<10 ; i++)
   {
      int tmp = i;
      Thread th = new Thread(() => Console.Write(tmp) );
      th.Start();
   }
}

Leider führt auch das nicht zum gewünschten Ergebnis, wie die folgenden Screenshots zeigen.

csharp-threads-37.jpg

csharp-threads-38.jpg
















Valid XHTML 1.0 Strict top Back Next Up Home