373

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.

vvvvv
  • 25,404
  • 19
  • 49
  • 81
Neal
  • 9,487
  • 15
  • 58
  • 101

3 Answers3

699

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<T>, or (if you must) void.
  • Any type that follows a certain pattern can be awaitable. The most common awaitable types are Task and Task<T>.

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    You said: "There are some async methods whose return types are not awaitable". Do you mean "async void" methods? If not, please give any example. – sgnsajgon Sep 04 '14 at 22:12
  • 12
    @sgnsajgon: Yes. `async` methods must return `Task`, `Task`, or `void`. `Task` and `Task` are awaitable; `void` is not. – Stephen Cleary Sep 05 '14 at 00:50
  • 3
    Actually, a `async void` method signature will compile, it's just a quite terrible idea as you loose your pointer to your async task – IEatBagels Nov 21 '14 at 20:54
  • 4
    @TopinFrassi: Yes, they will compile, but `void` is not awaitable. – Stephen Cleary Nov 21 '14 at 21:17
  • Hi @StephenCleary if one simply must run an awaitable Task synchronously (e.g. due to some external constraints) is the safest way to do this something like `var result = Task.Run(() => AwaitableTaskIMustUse()).Result;`? – GFoley83 Apr 14 '16 at 06:09
  • 1
    @GFoley83: There's no universal answer. I cover a variety of approaches in my article on [brownfield async development](https://msdn.microsoft.com/en-us/magazine/mt238404.aspx). You can use `Task.Run(() => SomethingAsync()).GetAwaiter().GetResult()` only if `SomethingAsync` can safely be run on a thread pool thread and your app is not in danger of thread pool starvation. – Stephen Cleary Apr 14 '16 at 12:25
  • @StephenCleary sorry for my late response but I'm really trying to figure out something. I've read your blog post: http://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html and you've mentioned there that using Task.Run in the Async implementation is a bad practice. Isn't it what you're doing in DoVariousThingsFromTheUIThreadAsync() ? And I see that kind of use also in the answer below (DoWorkAsync() method which uses Task.Run(..)).. – ohadinho Feb 16 '17 at 05:50
  • 4
    @ohadinho: No, what I'm talking about in the blog post is when the entire method is just a call to `Task.Run` (like `DoWorkAsync` in this answer). Using `Task.Run` to *call* a method from a UI context is appropriate (like `DoVariousThingsFromTheUIThreadAsync`). – Stephen Cleary Feb 16 '17 at 14:15
  • Thought not directly related but want to ask, in the context of asp.net web api which is implicitly multithreaded, from a controller if I have to consume an external REST api which only provides synchronous methods, is it beneficial to wrap the synchronous api in Task.Run()? – dragonfly02 Mar 15 '17 at 23:21
  • @stt106: No. If it's a REST API, you should be able to call it asynchronously (using, e.g., `HttpClient`). – Stephen Cleary Mar 16 '17 at 12:26
  • Sorry I probably confused you. It's an external .net api for cloud data access; and the api doesn't provide any asynchronous methods. – dragonfly02 Mar 16 '17 at 12:33
  • @stt106: [Then just call them synchronously; don't wrap them in `Task.Run`](https://msdn.microsoft.com/en-us/magazine/dn802603.aspx). – Stephen Cleary Mar 16 '17 at 12:34
  • Because it actually increases context switch since Task.Run will use another pool thread? Even though the initial request thread is unblocked it actually blocks another pool thread Task.Run uses? – dragonfly02 Mar 16 '17 at 12:37
  • @stt106: Yes; follow the link I gave for more information. – Stephen Cleary Mar 16 '17 at 12:38
  • @StephenCleary -- I just read your article (the last link you shared above, from Mar 16), and I understand how yielding on thread pool threads can be beneficial for truly-async operations (the "there is no thread" kind, like I/O) because there's no blocking and the thread can do other things. But what about if we're talking about pseudo-async? E.g. I have an old library that is synchronous but long-running methods. I want to kick them off in a task and yield, but won't they still have to run on (and thus block) a thread somewhere? They're not the "there is no thread" kind of operations. – rory.ap Jun 07 '17 at 17:58
  • ... well, to some extent, deep down inside, they include I/O operations, which would be truly async, but that's only part of what these methods (from the old library) are doing. I want to make the whole call to the method asynchronous, but it still seems like the actual code, while yielding to the main thread, would still run "synchronously" on a thread pool thread (e.g. via Task.Run()). – rory.ap Jun 07 '17 at 18:01
  • Example: `var t = await Task.Run(() => MyOldLibrarySynchronousMethod()); // is this blocking a thread pool thread, even though it yields to the calling thread?` – rory.ap Jun 07 '17 at 18:03
  • @rory.ap: Yes, synchronous I/O in a `Task.Run` will block a thread pool thread. It's fine to call old sync methods with `await Task.Run`, but you don't want to expose an async-looking wrapper - that's what I call "fake async". You may want to check out my [`Task.Run` etiquette series](https://blog.stephencleary.com/2013/10/taskrun-etiquette-and-proper-usage.html). – Stephen Cleary Jun 07 '17 at 18:54
  • @StephenCleary -- Thank you very much! So, the reason I don't want to have a "fake async" method is simply because it's surprising -- developers think it's truly-async? Whereas if I do `await Task.Run` to call a sync method, it's clear what it will do, i.e. run a sync method on a background thread and block the entire thread? – rory.ap Jun 08 '17 at 17:18
  • 5
    Yes, exactly. It's valid to use `Task.Run` to *invoke* a method, but if there's a `Task.Run` around all (or almost all) of the method's code, then that's an anti-pattern - just keep that method synchronous and move the `Task.Run` up a level. – Stephen Cleary Jun 08 '17 at 19:32
  • c# threading models are a mess. that's why I prefer Qt's event loops/signal/slots. – Aminos Jun 14 '22 at 09:37
  • 2
    I'm not immensely proud having put the 666th "+1" to this thread. And many thanks for this article. – Fredy Aug 14 '22 at 13:55
30

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();
}
Beltway
  • 508
  • 4
  • 17
Ronald Ramos
  • 5,200
  • 2
  • 15
  • 12
  • 38
    Why do you use TaskCompletionSource, instead of just returning task returned by Task.Run() method (and changing its body to return result)? – ironic Jun 21 '16 at 13:39
  • 7
    Just a side note. A method that has a "async void" signature is generally bad practice and considered bad code as it can lead to UI deadlock pretty easily. The main exception is asynchronous event handlers. – Jazzeroki Oct 28 '16 at 14:33
  • 1
    No idea why `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. – Sasino Dec 06 '21 at 07:06
  • The trouble is with `async void` is that the caller has no way to await the outcome of the method. They have no access to the `Task`. So you're essentially ignoring the task . – metoyou Sep 14 '22 at 15:17
19

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.

Craig.Feied
  • 2,617
  • 2
  • 16
  • 25
zheng yu
  • 297
  • 3
  • 10
  • So if I have to consume a synchronous external api within a web api controller, I should NOT wrap the synchronous call in Task.Run()? As you said, doing so will keep the initial request thread unblocked but it's using another pool thread to call the external api. In fact I think it's still a good idea because doing this way it can in theory use two pool threads to process many requests e.g. one thread can process many incoming requests and another one can call the external api for all these requests? – dragonfly02 Mar 15 '17 at 23:31
  • I agree.I am not saying you shouldn't absolutely wrap all synchronous calls within Task.Run(). I am simply pointing out potential issue. – zheng yu Mar 22 '17 at 08:55
  • 3
    @stt106 `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 – Saeb Amini Aug 10 '17 at 23:41