I'm trying to understand async await in the simplest form. I want to create a very simple method that adds two numbers for the sake of this example, granted, it's no processing time at all, it's just a matter of formulating an example here.
Example 1
private async Task DoWork1Async()
{
int result = 1 + 2;
}
Example 2
private async Task DoWork2Async()
{
Task.Run( () =>
{
int result = 1 + 2;
});
}
If I await DoWork1Async()
will the code run synchronously or asynchronously?
Do I need to wrap the sync code with Task.Run
to make the method awaitable AND asynchronous so as not to block the UI thread?
I'm trying to figure out if my method is a Task
or returns Task<T>
do I need to wrap the code with Task.Run
to make it asynchronous.
I see examples on the net where people are awaiting code that has nothing async within and not wrapped in a Task.Run
or StartNew
.
First, let's clear up some terminology: "asynchronous" (async
) means that it may yield control back to the calling thread before it starts. In an async
method, those "yield" points are await
expressions.
This is very different than the term "asynchronous", as (mis)used by the MSDN documentation for years to mean "executes on a background thread".
To futher confuse the issue, async
is very different than "awaitable"; there are some async
methods whose return types are not awaitable, and many methods returning awaitable types that are not async
.
Enough about what they aren't; here's what they are:
The async keyword allows an asynchronous method (that is, it allows await expressions). async methods may return Task, Task
Any type that follows a certain pattern can be awaitable. The most common awaitable types are Task and Task
So, if we reformulate your question to "how can I run an operation on a background thread in a way that it's awaitable", the answer is to use Task.Run
:
private Task<int> DoWorkAsync() // No async because the method does not need await
{
return Task.Run(() =>
{
return 1 + 2;
});
}
(But this pattern is a poor approach; see below).
But if your question is "how do I create an async
method that can yield back to its caller instead of blocking", the answer is to declare the method async
and use await
for its "yielding" points:
private async Task<int> GetWebPageHtmlSizeAsync()
{
var client = new HttpClient();
var html = await client.GetAsync("http://www.example.com/");
return html.Length;
}
So, the basic pattern of things is to have async
code depend on "awaitables" in its await
expressions. These "awaitables" can be other async
methods or just regular methods returning awaitables. Regular methods returning Task
/Task<T>
can use Task.Run
to execute code on a background thread, or (more commonly) they can use TaskCompletionSource<T>
or one of its shortcuts (TaskFactory.FromAsync
, Task.FromResult
, etc). I don't recommend wrapping an entire method in Task.Run
; synchronous methods should have synchronous signatures, and it should be left up to the consumer whether it should be wrapped in a Task.Run
:
private int DoWork()
{
return 1 + 2;
}
private void MoreSynchronousProcessing()
{
// Execute it directly (synchronously), since we are also a synchronous method.
var result = DoWork();
...
}
private async Task DoVariousThingsFromTheUIThreadAsync()
{
// I have a bunch of async work to do, and I am executed on the UI thread.
var result = await Task.Run(() => DoWork());
...
}
I have an async
/await
intro on my blog; at the end are some good followup resources. The MSDN docs for async
are unusually good, too.
One of the most important thing to remember when decorating a method with async is that at least there is one await operator inside the method. In your example, I would translate it as shown below using TaskCompletionSource.
private Task<int> DoWorkAsync()
{
//create a task completion source
//the type of the result value must be the same
//as the type in the returning Task
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
Task.Run(() =>
{
int result = 1 + 2;
//set the result to TaskCompletionSource
tcs.SetResult(result);
});
//return the Task
return tcs.Task;
}
private async Task DoWork()
{
int result = await DoWorkAsync();
}
async void
is considered "bad practice", there are plenty of applications you would use it for, basically any time you need to do something that you don't care when it ends.
When you use Task.Run to run a method, Task gets a thread from threadpool to run that method. So from the UI thread's perspective, it is "asynchronous" as it doesn't block UI thread.This is fine for desktop application as you usually don't need many threads to take care of user interactions.
However, for web application each request is serviced by a thread-pool thread and thus the number of active requests can be increased by saving such threads. Frequently using threadpool threads to simulate async operation is not scalable for web applications.
True Async doesn't necessarily involving using a thread for I/O operations, such as file / DB access etc. You can read this to understand why I/O operation doesn't need threads. http://blog.stephencleary.com/2013/11/there-is-no-thread.html
In your simple example,it is a pure CPU-bound calculation, so using Task.Run is fine.
I should NOT wrap the synchronous call in Task.Run()
that's correct. If you do, you'd just be switching threads. i.e. you're unblocking the initial request thread but you're taking another thread from the threadpool which could have been used to process another request. The only outcome is a context switch overhead when the call is completed for absolutely zero gain
Success story sharing
async
methods must returnTask
,Task<T>
, orvoid
.Task
andTask<T>
are awaitable;void
is not.async void
method signature will compile, it's just a quite terrible idea as you loose your pointer to your async taskvoid
is not awaitable.Task.Run
(likeDoWorkAsync
in this answer). UsingTask.Run
to call a method from a UI context is appropriate (likeDoVariousThingsFromTheUIThreadAsync
).Task.Run
to invoke a method, but if there's aTask.Run
around all (or almost all) of the method's code, then that's an anti-pattern - just keep that method synchronous and move theTask.Run
up a level.