Skip to content

Files

Latest commit

0778e87 · Jan 10, 2022

History

History
64 lines (48 loc) · 2.55 KB

PH_S034.md

File metadata and controls

64 lines (48 loc) · 2.55 KB

PH_S034 - Async Lambda Inferred to Async Void

Problem

C# may infer a lambda expression to async void, depending on the target type. This inference means that neither the asynchronous operation of it can be awaited nor a potential exception may be handled. Furthermore, any unhandled exception of this async lambda may lead to a crash of the application.

For example, see the code below. The interface IWorkQueue accepts a callback of type Action. The DownloadFile method provides an inline implementation with an async lambda. This lambda expression will be inferred to async void; thus, the completion of the asynchronous operation cannot be awaited by the implementation of IWorkQueue. Furthermore, an exception handler will be ineffective.

interface IWorkQueue {
  // Method only accepts synchronous callbacks
  void ScheduleWork(Action job);
}

class FileDownloader {
  private readonly WebClient webClient;
  private readonly IWorkQueue queue;

  public void DownloadFile(Uri address, StreamWriter output) {
    queue.ScheduleWork(
      async () => {  // Will be inferred as async void
        var body = await webClient.DownloadStringTaskAsync(address);
        await output.WriteAsync(body);
      }
    );
  }
}

This analysis is related to the more specific case of PH_S014. Moreover, it leads to the same problem as PH_S030 illustrates.

Solution

First and foremost, try to make the receiving side accept asynchronous callbacks. For the example above, the solution would be adding an overload that accepts a parameter of the type Func<Task>:

interface IWorkQueue {
  void ScheduleWork(Action job);
  
  // This new overload will allow implementations to handle the asynchronous operations properly.
  void ScheduleWork(Func<Task> job);
}

If it's impossible to adapt the receiving side, consider switching to a purely synchronous implementation without using async and await.

Additional Note

Mind that asynchronous event handlers (e.g., in a WPF application) are the only exception where async void can be used. For example, the following code is fine:

interface IFileDownloadView {
  public event Action OnButtonClicked;
}

class FileDownloader {
  public void Register(IFileDownloadView view) {
    // Register an asynchronous event handler to the event OnButtonClicked.
    view.OnButtonClicked += async () => {
      // async body
    }
  }
}