These days it’s not uncommon for me to receive an email or read a forum post from someone concerned about a problem they’re experiencing with an async method they’ve written, and they’re seeking help debugging the issue. Sometimes plenty of information about the bug is conveyed, but other times the communication is void of anything more than a root problem statement. That’s when I engage my powers of psychic debugging to suggest what the root cause might be, without actually knowing more about the developer’s codebase.
Here are four of the more common issues I’ve heard raised, along with their likely culprits. If you experience one of these problems, look at one of these causes first, as there’s a very good chance it’s to blame.
1. “I converted my synchronous method to be asynchronous using ‘async’, but it still runs synchronously.”
As explained in the Async/Await FAQ, marking a method as ‘async’ does not force the method to run asynchronously, e.g. it doesn’t queue a work item to run the method, it doesn’t spawn a new thread to run the method, etc. Marking a method as ‘async’ really just tells the compiler to allow usage of ‘await’ inside the body of the method, and to handle completion/results/exceptions from the method in a special way (i.e. putting them into a task that’s returned from the method call). When you invoke a method that’s marked as ‘async’, the method is still invoked synchronously, and it continues to run synchronously until either a) the method completes, or b) the method awaits an awaitable (e.g. a task) that’s not yet complete. If the method doesn’t contain any awaits (which will generate a compiler warning) or if it only ever awaits awaitables that are already completed, the method will run to completion synchronously, and the task returned to the caller will be completed by the time it’s handed back. This is by design.
If your goal in using ‘async’ isjust to offload some synchronous work to another thread, you can instead use Task.Run to do this rather than using the async/await language features, e.g. if you have the synchronous method:
int DoWork()
{
…
}
instead of converting that to the following (which isn’t doing what you wanted, as the method will run entirely synchronously due to a lack of awaits):
async Task<int> DoWorkAsync()
{
… // same code as in the synchronous method
}
Read more: Parallel Programming with .NET
QR: