Asynchrone Methodenaufrufe im .NET Framework

Veröffentlicht: 09. Jan 2002 | Aktualisiert: 13. Jun 2004

Von Michael Willers

Wird alles anders?

Frei in Anlehnung an "Bleibt alles anders" von Herbert Grönemeyer lässt sich die Programmierung von asynchronen Methodenaufrufen im .NET Framework beschreiben. Der Grundgedanke, solche Aufrufe über einen systemeigenen Threadpool auszuführen, ist geblieben. Die Programmierung erfolgt hingegen gänzlich anders als unter COM. Allerdings im positiven Sinne - sie wird einfacher.

Asynchrone Aufrufe werden grundsätzlich über Delegates abgebildet. Unter einem Delegate versteht man die von C++ her bekannten, typisierten Funktionszeiger. Sie sind im .NET Framework fester Bestandteil des Typsystems (Common Type System) und stehen somit allen .NET Sprachen zur Verfügung.

Sobald Sie eine Variable vom Typ delegate deklarieren, wird beim Übersetzen von den .NET Compilern entsprechender IL-Code für asynchrone Aufrufe erzeugt. Im Wesentlichen sind dies die Methoden BeginInvoke und EndInvoke.
Mit dem Aufruf von BeginInvoke wird ein Workerthread gestartet, der den Methodenaufruf ausführt. Der Aufruf von EndInvoke beendet diesen Thread wieder.

Allerdings kann es passieren, dass Ihr Programm dennoch "blockiert". Dies ist genau dann der Fall, wenn EndInvoke aufgerufen wird bevor die aufgerufene Methode mit Ihrer Arbeit fertig ist. Dann "wartet" EndInvoke, solange bis die Methode fertig ist.
Um dies zu verhindern, können Sie mit Hilfe einer weiteren Methode IsCompleted fragen, ob der Methodenaufruf bereits zurückgekehrt ist. Listing 1 zeigt ein Beispiel mit der konkreten Codierung in C#.

Listing1: Asynchrone Methodenaufrufe sind per Polling-Verfahren sehr leicht zu realisieren

using System; 
class Calc 
{ 
public static double Add(double d1, double d2) { return (d1 + d2); } 
public static double Sub(double d1, double d2) { return (d1 - d2); } 
} 
class EntryPoint 
{ 
// delegate deklarieren 
delegate double Add(double x, double y); 
static double _resu< 
// asynchroner Methodenaufruf 
static double DoSomething(Add add) 
{ 
IAsyncResult ar = add.BeginInvoke(3,4,null,null); 
// Polling, sonst könnte EndInvoke blockieren 
while (!ar.IsCompleted) { Console.Write("."); } 
double result = add.EndInvoke(ar); 
return resu< 
} 
static void Main() 
{ 
Add addMethod = new Add(Calc.Add); 
_result = DoSomething(addMethod); 
Console.WriteLine(_result); 
} 
}

Allerdings hat diese Methode einen entscheidenden Nachteil. Sie müssen ein Polling implementieren, um den Status in regelmäßigen Abständen abzufragen. Die Alternative: Sie implementieren eine Callback-Funktion, die automatisch aufgerufen wird, wenn der Workerthread seine Arbeit beendet hat.
Da Sie nun nicht mehr direkt auf das Ende des Methodenaufrufs warten, müssen Sie allerdings auch die Koordination zwischen Workerthread und Programmthread selbst übernehmen. Ein Blick in die MSDN Dokumentation des .NET Framework SDKs hilft hier weiter, denn genau zu diesem Zweck bietet das .NET Framework eigene Klassen an.
Listing 2 zeigt die Implementationsdetails mit einem C# Programm - die Programmierung per Callback ist zwar nicht so trivial wie die Polling-Methode, bietet aber deutlich mehr Flexibilität.

Listing 2: Asynchrone Methodenaufrufe per Callback bedürfen einer sorgfältigen Threadsynchronisation

using System; 
using System.Threading; 
class Calc 
{ 
public static double Add(double d1, double d2) { return (d1 + d2); } 
public static double Sub(double d1, double d2) { return (d1 - d2); } 
} 
class EntryPoint 
{ 
// delegate deklarieren 
delegate double Add(double x, double y); 
static double _resu< 
// Signal für Threadsynchronisation 
static AutoResetEvent done = new AutoResetEvent(false); 
// Funktion, die beim Methodenende aufgerufen wird 
static void CallBackProc(IAsyncResult call) 
{ 
Add add = (Add)call.AsyncState; 
_result = add.EndInvoke(call); 
// wir sind fertig 
Console.Write("--- Fertig! ---"); 
done.Set(); 
} 
// asynchroner Methodenaufruf 
static double DoSomething(Add add) 
       { 
AsyncCallback cb = new AsyncCallback(CallBackProc); 
IAsyncResult ar = add.BeginInvoke(3,4,cb,add); 
// Do some work, anstelle des Polling ;-) 
for (int i = 0; i < 10000; i++) { Console.Write("."); } 
// Wenn die Methode eher als der Workerthread 
// fertig ist, dann auf Ende des Threads warten 
done.WaitOne(); 
return _resu< 
} 
static void Main() 
{ 
// Add-Methode deklarieren 
Add addMethod = new Add(Calc.Add); 
_result = DoSomething(addMethod); 
Console.WriteLine(_result); 
} 
}

Soviel zur grundsätzlichen Funktionsweise von asynchronen Aufrufen im .NET Framework. Bliebe abschließend die Frage zu klären, woher die Workerthreads kommen, die per BeginInvoke angestossen werden.
Sie kommen aus den Threadpool, den die Common Language Runtime vorhält. Damit schließt sich der Kreis, denn auch COM benutzt für asynchrone Aufrufe einen Threadpool, der durch die COM-Laufzeitumgebung bereitgestellt wird.