<commit_before>    title: Why is `async void` Wrong and What To Do Instead?
    description: `async void` is one of the controversial C# features. In this post, I'm discussing its downsides and an alternate approach.
---

C# is a language known for its feature-richness and maybe even being a bit over the top with its syntax sugar. And one of its signature features is definitely seamless asynchronous code integration with the `async` and `await` keywords.

One lesser known feature that was introduced in the same version of C# is `async void`. An `async void` method is similar to a regular `async` (e.g. `async Task`), but returns nothing, a `void`. The main motivation behind this feature was an ability to subscribe such a method to a .NET event: event handlers weren't upgraded to properly support methods with non-`void` return types. For example, consider this:

```csharp
async void ProcessEvent(object sender, EventArgs args)
{
    await DoThis();
    await DoThat();
}

event EventHandler Foo;
// …
Foo += ProcessEvent;
```

Looks simple enough (even though people have started using this for purposes other than mere event handling), what are the downsides?

## Observability

First of all, even if the caller cares, they now cannot observe the method's execution. They cannot `await` a result of a call to an `async void` method.

`async void` was designed for "fire-and-forget" calls, so this is not a problem in most cases, but still: sometimes you want to observe the result of such a call, and yet you cannot.

## Exception handling

Now this is very problematic, so let's start from the beginning.

The `async` methods were introduced in C# 5.0, released together with .NET Framework 4.5. Before .NET Framework 4.5, `Task`s had [a problem with unhandled exceptions][dev-blogs.task-exception-handling]: if a `Task` was failed with an exception, then if would rethrow it on finalization, and the runtime would crash afterwards (because an exception is thrown on the finalizer thread).

It is still possible to enable this behavior on modern .NET Framework versions with a runtime configuration option:
```xml
<configuration>
    <runtime>
        <ThrowUnobservedTaskExceptions enabled="true"/>
    </runtime>
</configuration>
```

Also, I should note that it's possible to write `async` C# code while targeting older runtime versions, where the default behavior is to _throw_ the exception (and thus eventually terminate the runtime).

1. So, the first problem with `async void` is: **any exception thrown from such a method may terminate some versions of .NET Framework**, either by default or if configured accordingly. Modern defaults are tuned for safer execution, so you'll rarely encounter this problem, but still, it's possible.
2. Our second problem comes from the same direction: if the exception is not thrown, then what? How will we know that it happened at all? **By default, an exception thrown from an `async void` method is unobserved**, and nobody will know about it. It's possible to subscribe to `TaskScheduler::UnobservedTaskException` event and observe the exceptions, but that's a global solution to a local problem, and should be done carefully.
3. The third problem aptly comes from the third-party code. Namely, third-party task schedulers. For example, consider that you're writing an Avalonia application. Avalonia message loop is pretty fragile (at least in 0.10.18), and the whole application will crash if any exception is thrown during the message processing. And `async void` method throwing an exception will crash the Avalonia's message scheduler. **Any similarly-written task scheduler will behave the same way: it may crash on unobserved task exception, including the one from an `async void` method.**

## The solution

All of these points create a dangerous situation. In most cases, if you're careful enough, you may use `async void` and get away with it, but that becomes more tedious more and more code you write. Forgot a `try … catch` block in an `async void` method? Goodbye, the app will crash. Forgot or incorrectly handled a `TaskScheduler::UnobservedTaskException`? The exception will go unobserved.

There's a solution to this madness: just never use `async void`, and you'll have much fewer problems to care about. You still occasionally need "fire-and-forget" tasks, of course: being it in an event handler or whatever. In this case, create a `NoAwait` extension method which will catch and log your exceptions. Consider this rewritten snippet from the beginning of the post:

```csharp
void ProcessEvent(object sender, EventArgs args) // note no `async`
{
    async Task Go()
    {
        await DoThis();
        await DoThat();
    }
    Go().NoAwait();
}

event EventHandler Foo;
// …
Foo += ProcessEvent;
```

Your `NoAwait()` should use your logging framework to report an exception. But for the sake of simplicity, here's an example of such method:
```csharp
void NoAwait(this Task task)
{
    task.ContinueWith(t =>
    {
        if (t.IsFaulted) Console.Error.WriteLine(t.Exception);
    }, TaskContinuationOptions.ExecuteSynchronously);
}
```

An example from the production code you may find [here][code.no-await].

[code.no-await]: https://github.com/JetBrains/rd/blob/b8dce02e660ee3bce10f02a3a6cbd3981758379a/rd-net/Lifetimes/Threading/TaskEx.cs#L18-L26
[dev-blogs.task-exception-handling]: https://devblogs.microsoft.com/pfxteam/task-exception-handling-in-net-4-5/
<commit_msg>Posts: add a point, fix headers<commit_after>    title: Why is `async void` Wrong and What To Do Instead?
    description: `async void` is one of the controversial C# features. In this post, I'm discussing its downsides and an alternate approach.
---

C# is a language known for its feature-richness and maybe even being a bit over the top with its syntax sugar. And one of its signature features is definitely seamless asynchronous code integration with the `async` and `await` keywords.

One lesser known feature that was introduced in the same version of C# is `async void`. An `async void` method is similar to a regular `async` (e.g. `async Task`), but returns nothing, a `void`. The main motivation behind this feature was an ability to subscribe such a method to a .NET event: event handlers weren't upgraded to properly support methods with non-`void` return types. For example, consider this:

```csharp
async void ProcessEvent(object sender, EventArgs args)
{
    await DoThis();
    await DoThat();
}

event EventHandler Foo;
// …
Foo += ProcessEvent;
```

Looks simple enough (even though people have started using this for purposes other than mere event handling), what are the downsides?

## Observability

First of all, even if the caller cares, they now cannot observe the method's execution. They cannot `await` a result of a call to an `async void` method.

`async void` was designed for "fire-and-forget" calls, so this is not a problem in most cases, but still: sometimes you want to observe the result of such a call, and yet you cannot.

## Exception Handling

Now this is very problematic, so let's start from the beginning.

The `async` methods were introduced in C# 5.0, released together with .NET Framework 4.5. Before .NET Framework 4.5, `Task`s had [a problem with unhandled exceptions][dev-blogs.task-exception-handling]: if a `Task` was failed with an exception, then if would rethrow it on finalization, and the runtime would crash afterwards (because an exception is thrown on the finalizer thread).

It is still possible to enable this behavior on modern .NET Framework versions with a runtime configuration option:
```xml
<configuration>
    <runtime>
        <ThrowUnobservedTaskExceptions enabled="true"/>
    </runtime>
</configuration>
```

Also, I should note that it's possible to write `async` C# code while targeting older runtime versions, where the default behavior is to _throw_ the exception (and thus eventually terminate the runtime).

1. So, the first problem with `async void` is: **any exception thrown from such a method may terminate some versions of .NET Framework**, either by default or if configured accordingly. Modern defaults are tuned for safer execution, so you'll rarely encounter this problem, but still, it's possible.
2. Our second problem comes from the same direction: if the exception is not thrown, then what? How will we know that it happened at all? **By default, an exception thrown from an `async void` method is unobserved**, and nobody will know about it. It's possible to subscribe to `TaskScheduler::UnobservedTaskException` event and observe the exceptions, but that's a global solution to a local problem, and should be done carefully.
3. The third problem aptly comes from the third-party code. Namely, third-party task schedulers. For example, consider that you're writing an Avalonia application. Avalonia message loop is pretty fragile (at least in 0.10.18), and the whole application will crash if any exception is thrown during the message processing. And `async void` method throwing an exception will crash the Avalonia's message scheduler. **Any similarly-written task scheduler will behave the same way: it may crash on unobserved task exception, including the one from an `async void` method.**

## The Solution

All of these points create a dangerous situation. In most cases, if you're careful enough, you may use `async void` and get away with it, but that becomes more tedious more and more code you write. Forgot a `try … catch` block in an `async void` method? Goodbye, the app will crash. Forgot or incorrectly handled a `TaskScheduler::UnobservedTaskException`? The exception will go unobserved.

There's a solution to this madness: just never use `async void`, and you'll have much fewer problems to care about. You still occasionally need "fire-and-forget" tasks, of course: being it in an event handler or whatever. In this case, create a `NoAwait` extension method which will catch and log your exceptions. Consider this rewritten snippet from the beginning of the post:

```csharp
void ProcessEvent(object sender, EventArgs args) // note no `async`
{
    async Task Go()
    {
        await DoThis();
        await DoThat();
    }
    Go().NoAwait();
}

event EventHandler Foo;
// …
Foo += ProcessEvent;
```

Your `NoAwait()` should use your logging framework to report an exception. But for the sake of simplicity, here's an example of such method:
```csharp
void NoAwait(this Task task)
{
    task.ContinueWith(t =>
    {
        if (t.IsFaulted) Console.Error.WriteLine(t.Exception);
    }, TaskContinuationOptions.ExecuteSynchronously);
}
```

An example from the production code you may find [here][code.no-await].

Last but not least, this solution is easy to automate. There are (or at least it's easy to create) compiler diagnostics that guard you from `async void` and ignored tasks (without continuations or `await`), but it would be harder to, say, make you wrap all the code inside an `async void` method into a `try … catch` block.

[code.no-await]: https://github.com/JetBrains/rd/blob/b8dce02e660ee3bce10f02a3a6cbd3981758379a/rd-net/Lifetimes/Threading/TaskEx.cs#L18-L26
[dev-blogs.task-exception-handling]: https://devblogs.microsoft.com/pfxteam/task-exception-handling-in-net-4-5/

{'fraction_non_alphanumeric': 0.07440291219035781, 'fraction_numerical': 0.007635621060108319, 'mean_word_length': 4.417989417989418, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 15, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2758189', 'n_tokens_mistral': 3032, 'n_tokens_neox': 2859, 'n_words': 1593}