The problem with using “async” and “void” in programming languages like C# is that it can lead to unexpected behavior and difficulties in error handling. Let’s take a closer look at why this is a problem:
We develop a small console application with the following flow:
1.) First, we issue a message (“Start”) on the console.
2.) Then, we start an asynchronous task that waits for one second before issuing the message “Wait one second” on the console.
3.) After the asynchronous task finishes waiting, it returns to the main thread.
4.) The main thread waits until the asynchronous task completes.
5.) Only when the task completes, the “End” message is displayed on the console.
In this way, we ensure that the application responds synchronously to the execution of the asynchronous task and continues only when the task completes.
So far everything is running smoothly. However, I currently have to wait for the background task to complete. Even though it is an asynchronous operation that does not return a result, my program blocks until this task is fully completed.
But what if I wanted to implement a simple “fire & forget” method? What if I didn’t want to wait for the process to complete?
Theoretically, we could simply use the “void” return type in the background task instead of “task” and remove the “await” statement. This way, the method would execute asynchronously without waiting for the result. Would this be possible?
Yes, the compiler would not detect an error, and the application could still be started.
The output would look a little different than it does now, but that is exactly what is intended. We call the method without waiting.
The method should be called and our application should then continue without waiting for it to finish. This has been implemented correctly.
However, care should be taken because there is a danger here.
Let’s take a closer look at the background process:
In our example, not much is done in this phase. However, we should note what would happen if an error occurs inside this or another method or function is called in this method
Let’s add another one to our background task:
In our extension an error occurs after a certain time, e.g. due to an incorrect execution of an operation.
How will our application react in this case?
This application will crash.
In our example, if we did not wait for an input, the application would crash without an error message for no obvious reason.
Can’t we solve the problem by implementing error handling?
Unfortunately, the problem is not solved by a simple implementation of error handling.
To explain this better, I would like to take a small diagram for help.
The error occurs in the subthread without notifying the main thread, which causes the program to terminate.
Overall, you should use “async task” instead of “async void” to avoid the above problems. This allows you to wait for the async task to complete, retrieve results, and handle errors properly, resulting in more reliable and easier-to-maintain code.
The problem here is the ForEach loop. Let’s take a look inside this loop.
This is the code from Microsoft: System.Collections.Generic
The parameter in the ForEach loop takes an action, i.e. a method. Methods do not provide a return value; ergo we run into the exact problem described above!
This means that we encounter the same problem here as described above.
Summary:
1. Loss of information: If you declare a method as “async void,” it means that the method executes asynchronous code but does not return a value. This results in you not having access to the result of the method, even if it actually returns a result. It becomes difficult to use the result or pass it to other parts of your code. This can lead to behavior that is difficult to understand, especially when coordinating asynchronous operations.
2. Leaving the logical flow: By using “async void” the application leaves the logical flow of the application at the point where this method is called and is processed uncontrolled. This is because the method with async void does not have a return value that can be monitored.
3. Missing return values: In C# and other programming languages, it is a best practice to declare methods that contain asynchronous code with “async task”. This signals that the method returns a task that allows you to monitor the asynchronous operation and wait for its completion if necessary. The absence of a return value with “async void” results in a limitation of your control over the asynchronous code.
4. Lack of error handling: When an async void method raises an exception, the exception is not caught or handled. This means that errors in the method go unhandled and can potentially crash the entire application process. In an asynchronous task, on the other hand, you can catch exceptions and respond accordingly.
Benjamin Witt
Macrix Lead Developer