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.
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
.
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
}
}
}