Hier geht es um die Frage, warum sollen wir die Verwendung von async-Methoden mit “void”-Rückgabewert vermeiden und stattdessen die async-Task-Methoden bevorzugen? Und warum erleichtern Async-Task-Methoden die Fehlerbehandlung, Erstellbarkeit und Testbarkeit?

Für async-Methoden gibt es drei mögliche Rückgabetypen:

  • Task, Task <T> und void
  • aber die standardmäßigen Rückgabetypen für async-Methoden sind nur Task und Task<T>

Beim Konvertieren von synchronem Code in asynchronen Code wird:

  • jede Methode, die einen Typ T zurückgibt, zu einer async-Methode, die Task <T> zurückgibt.
  • jede Methode, die void zurückgibt, wird zu einer async-Methode, die Task zurückgibt.

Ein Beispiel mit eine synchrone “void” und eine asynchrone Rückgabe :1)Async/Await – Bewährte Verfahren bei der asynchronen Programmierung – MSDN Magazine Issues – Stephen Cleary

// synchronous work
void MyMethod()
{
  Thread.Sleep(1000);
}

// asynchronous work
async Task MyMethodAsync()
{
  await Task.Delay(1000);
}

Return EventHandler


Async-Methoden, die void zurückgeben:

  • machen asynchrone EventHandler möglich
  • können einen EventHandler verwenden, der einen tatsächlichen Typ zurückgibt

Die Vorstellung aber, dass ein EventHandler etwas zurückgibt, erscheint nicht sinnvoll. EventHandler geben void zurück!

Ausnahmebehandlung


Async-void-Methoden haben eine andere Semantiken fürs Exception Handling:

  • Bei async-Task- oder async-Task<T>-Methode werden Exceptions im Task-Objekt erfasst.
  • Bei den async-void-Methoden gibt es kein Task-Objekt, sodass alle Exceptions einer async-void-Methode direkt im “SynchronizationContext” ausgelöst werden, der beim Starten der async-void-Methode aktiv war.

Exceptions einer async-void-Methode können nicht im “catch”-Block abgefangen werden.

private async void MyMethodThrowExceptionAsync()
{
  throw new InvalidOperationException();
}

public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
  try
  {
    MyMethodThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is never caught here!
    throw;
  }
}

Diese Exceptions können zwar mit einem Catch-All-Ereignis abgefangen werden, die Verwendung dieser Ereignisse für die allgemeine Ausnahmebehandlung erfordert jedoch einen sehr hohen Wartungsaufwand.

Start- und Endzeitpunkt


Async-void-Methoden weisen auch andere Erstellungssemantiken auf:

  • Async-Methoden, die Task oder Task<T> zurückgeben, können ganz einfach mit await, Task.WhenAny, Task.WhenAll usw. erstellt werden.
  • Async-Methoden, die void zurückgeben, bieten keine einfache Möglichkeit, dem aufrufenden Code mitzuteilen, dass sie abgeschlossen sind.

Es ist leicht, mehrere async-void-Methoden zu starten, es ist aber nicht leicht festzustellen, wann sie beendet wurden.

Tests


Async-void-Methoden sind schwierig zu testen. Aufgrund der Unterschiede in der Fehlerbehandlung und in der Erstellung ist es schwierig, Komponententests zu schreiben, die async-void-Methoden aufrufen.

Testbar


Besser wäre: Den Code in einem asynchronen EventHandler zu minimieren und ihn auf eine async-Task-Methode warten zu lasse, die die eigentliche Logik enthält.

private async void Button_Click(object sender, EventArgs e)
{
  await ButtonClickAsync();
}

public async Task ButtonClickAsync()
{
  // Do asynchronous work.
  await Task.Delay(1000);
}