When correctly use Task.Run and when just async-await

335,512

Solution 1

Note the guidelines for performing work on a UI thread, collected on my blog:

  • Don't block the UI thread for more than 50ms at a time.
  • You can schedule ~100 continuations on the UI thread per second; 1000 is too much.

There are two techniques you should use:

1) Use ConfigureAwait(false) when you can.

E.g., await MyAsync().ConfigureAwait(false); instead of await MyAsync();.

ConfigureAwait(false) tells the await that you do not need to resume on the current context (in this case, "on the current context" means "on the UI thread"). However, for the rest of that async method (after the ConfigureAwait), you cannot do anything that assumes you're in the current context (e.g., update UI elements).

For more information, see my MSDN article Best Practices in Asynchronous Programming.

2) Use Task.Run to call CPU-bound methods.

You should use Task.Run, but not within any code you want to be reusable (i.e., library code). So you use Task.Run to call the method, not as part of the implementation of the method.

So purely CPU-bound work would look like this:

// Documentation: This method is CPU-bound.
void DoWork();

Which you would call using Task.Run:

await Task.Run(() => DoWork());

Methods that are a mixture of CPU-bound and I/O-bound should have an Async signature with documentation pointing out their CPU-bound nature:

// Documentation: This method is CPU-bound.
Task DoWorkAsync();

Which you would also call using Task.Run (since it is partially CPU-bound):

await Task.Run(() => DoWorkAsync());

Solution 2

One issue with your ContentLoader is that internally it operates sequentially. A better pattern is to parallelize the work and then sychronize at the end, so we get

public class PageViewModel : IHandle<SomeMessage>
{
   ...

   public async void Handle(SomeMessage message)
   {
      ShowLoadingAnimation();

      // makes UI very laggy, but still not dead
      await this.contentLoader.LoadContentAsync(); 

      HideLoadingAnimation();   
   }
}

public class ContentLoader 
{
    public async Task LoadContentAsync()
    {
        var tasks = new List<Task>();
        tasks.Add(DoCpuBoundWorkAsync());
        tasks.Add(DoIoBoundWorkAsync());
        tasks.Add(DoCpuBoundWorkAsync());
        tasks.Add(DoSomeOtherWorkAsync());

        await Task.WhenAll(tasks).ConfigureAwait(false);
    }
}

Obviously, this doesn't work if any of the tasks require data from other earlier tasks, but should give you better overall throughput for most scenarios.

Share:
335,512
Lukas K
Author by

Lukas K

.NET developer - C#

Updated on July 20, 2022

Comments

  • Lukas K
    Lukas K almost 2 years

    I would like to ask you on your opinion about the correct architecture when to use Task.Run. I am experiencing laggy UI in our WPF .NET 4.5 application (with Caliburn Micro framework).

    Basically I am doing (very simplified code snippets):

    public class PageViewModel : IHandle<SomeMessage>
    {
       ...
    
       public async void Handle(SomeMessage message)
       {
          ShowLoadingAnimation();
    
          // Makes UI very laggy, but still not dead
          await this.contentLoader.LoadContentAsync();
    
          HideLoadingAnimation();
       }
    }
    
    public class ContentLoader
    {
        public async Task LoadContentAsync()
        {
            await DoCpuBoundWorkAsync();
            await DoIoBoundWorkAsync();
            await DoCpuBoundWorkAsync();
    
            // I am not really sure what all I can consider as CPU bound as slowing down the UI
            await DoSomeOtherWorkAsync();
        }
    }
    

    From the articles/videos I read/saw, I know that await async is not necessarily running on a background thread and to start work in the background you need to wrap it with await Task.Run(async () => ... ). Using async await does not block the UI, but still it is running on the UI thread, so it is making it laggy.

    Where is the best place to put Task.Run?

    Should I just

    1. Wrap the outer call because this is less threading work for .NET

    2. , or should I wrap only CPU-bound methods internally running with Task.Run as this makes it reusable for other places? I am not sure here if starting work on background threads deep in core is a good idea.

    Ad (1), the first solution would be like this:

    public async void Handle(SomeMessage message)
    {
        ShowLoadingAnimation();
        await Task.Run(async () => await this.contentLoader.LoadContentAsync());
        HideLoadingAnimation();
    }
    
    // Other methods do not use Task.Run as everything regardless
    // if I/O or CPU bound would now run in the background.
    

    Ad (2), the second solution would be like this:

    public async Task DoCpuBoundWorkAsync()
    {
        await Task.Run(() => {
            // Do lot of work here
        });
    }
    
    public async Task DoSomeOtherWorkAsync(
    {
        // I am not sure how to handle this methods -
        // probably need to test one by one, if it is slowing down UI
    }
    
  • Lukas K
    Lukas K over 10 years
    Thanks for your fast resp! I know the link you posted and saw the videos that are referenced in your blog. Actually that is why I posted this question - in the video is said (same as in your response) you should not use Task.Run in core code. But my problem is, that I need to wrap such method every time I use it to not slow down the responsivnes (please note all my code is async and is not blocking, but without Thread.Run it is just laggy). I am as well confused whether it is better approach to just wrap the CPU bound methods (many Task.Run calls) or completly wrap everything in one Task.Run?
  • Stephen Cleary
    Stephen Cleary over 10 years
    All your library methods should be using ConfigureAwait(false). If you do that first, then you may find that Task.Run is completely unnecessary. If you do still need Task.Run, then it doesn't make much difference to the runtime in this case whether you call it once or many times, so just do what's most natural for your code.
  • Drake
    Drake over 8 years
    I don't understand how the first technique will help him. Even if you use ConfigureAwait(false) on your cpu-bound method, it's still the UI thread that's going to do the cpu-bound method, and only everything after might be done on a TP thread. Or did I misunderstand something?
  • Stephen Cleary
    Stephen Cleary over 8 years
    @Darius: I recommend both techniques used together. This isn't two answers in one; it's one answer with two parts.
  • user4205580
    user4205580 over 8 years
    @StephenCleary If a method is CPU-bound, it should be called using Task.Run. When it's mixed (also I/O bound), it should be async and called using Task.Run. Could you explain why? I can't really see the need for Async signature.
  • user4205580
    user4205580 over 8 years
    DoWorkAsync apparently uses await in its body, so when we call it using Task.Run, the Task.Run can finish before DoWorkAsync does all its work. So the lines after await Task.Run(() => DoWorkAsync()) will be executed, even though DoWorkAsync hasn't finished yet.
  • user4205580
    user4205580 over 8 years
    I've just tested it: to make sure that no other lines are executed before DoWorkAsync finishes its work completely, one should call await Task.Run(async () => await DoWorkAsync());
  • Stephen Cleary
    Stephen Cleary over 8 years
    @user4205580: No, Task.Run understands asynchronous signatures, so it won't complete until DoWorkAsync is complete. The extra async/await is unnecessary. I explain more of the "why" in my blog series on Task.Run etiquette.
  • user4205580
    user4205580 over 8 years
    @StephenCleary Ok, makes sense why it didn't work with await Task.Run(() => {DoWorkAsync();}), but it works if I use Task.Run(() => DoWorkAsync())
  • Stephen Cleary
    Stephen Cleary over 8 years
    @user4205580: Yes, the extra brackets would make that lambda return void instead of Task/Task<T>. So () => { return DoWorkAsync(); } should also work.
  • user4205580
    user4205580 over 8 years
    Thanks. I've read a few of your articles, also the one that attempts to explain why using Task.Run is bad at the implementation level. I'd say sometimes the Task.Run is necessary (of course the point here is to wrap as few lines as possible in that call, not the whole body of my async method). If my async method doesn't use existing async methods, then there's no other way than just use await Task.Run in its code to make it truly async, right? Standard C# async methods use it I guess.
  • Stephen Cleary
    Stephen Cleary over 8 years
    @user4205580: No. The vast majority of "core" async methods do not use it internally. The normal way to implement a "core" async method is to use TaskCompletionSource<T> or one of its shorthand notations such as FromAsync. I have a blog post that goes into more detail why async methods don't require threads.
  • Michael Puckett II
    Michael Puckett II about 7 years
    You just need to call await DoWorkAsync(); in your example. No need to add another Task.Run(() => ...
  • Stephen Cleary
    Stephen Cleary about 7 years
    @MichaelPuckettII: The final example where I call DoWorkAsync inside Task.Run is only if DoWorkAsync contains CPU-bound code as well as I/O-bound code. The Task.Run would be required to prevent blocking the UI thread on the CPU-bound portion(s). For more information, see my series on Task.Run etiquette.
  • Michael Puckett II
    Michael Puckett II about 7 years
    As I look into this I keep wondering if Task was the type you meant to put on DoWorkAsync() or if there was meant to be more understanding behind it because I can't seem to get that flow to be correct.
  • Michael Puckett II
    Michael Puckett II about 7 years
    Oh well. I can't get this to paste code for some reason.
  • Stephen Cleary
    Stephen Cleary about 7 years
    @MichaelPuckettII See my example here.
  • VISHMAY
    VISHMAY about 6 years
    public async Task ExecuteTaskAsync() { //debug here & get thread id await Task.Run(() => LongTask()); //debug here & get thread id } public async Task LongTask(int No) { //debug here & get thread id await Task.Delay(5000); //debug here & get thread id } ------------------------------------ public async Task ExecuteTaskAsync() { //debug here & get thread id await LongTask(); //debug here & get thread id }
  • VISHMAY
    VISHMAY about 6 years
    @StephenCleary Lets say before task.run the thread was thread 9(id), inside Task.Run Allocated thread is 6, now during the execution of function I do hit await function of doworkasync and after await, it still captures thread 6. The direct call of async function behaves different, it changes thread id. So can you tell that as thread 6 is allocated to thread.run it is not releasing while having await? and that's why await is capturing thread 6 back? please refer my examples above.
  • Stephen Cleary
    Stephen Cleary about 6 years
    @VISHMAY: I recommend that you ask your own SO question. It's hard to write/read code in SO comments.
  • Shaul Behr
    Shaul Behr over 4 years
    @StephenCleary why is it bad practice to use Task.Run() on I/O bound processes?
  • Stephen Cleary
    Stephen Cleary over 4 years
    @ShaulBehr: It's always ideal to use asynchronous code for I/O. In a client GUI app, using Task.Run to block a thread on I/O is an acceptable workaround, causing a minor but acceptable performance hit in exchange for a more responsive UI. Using Task.Run on the server side is where the "bad practice" comes in: it's almost always a bad idea, causing a decrease in performance for no benefit at all.
  • Eugene Podskal
    Eugene Podskal over 4 years
    Not sure, but shouldn't the last code snippet be await Task.Run(async () => await DoWorkAsync());? Is it a typo, or am I missing something?
  • Stephen Cleary
    Stephen Cleary over 4 years
    @EugenePodskal: It can be written either way. Eliding async and await is common when it's just a simple passthrough like this.
  • Eugene Podskal
    Eugene Podskal over 4 years
    Sorry for bothering, forgot about blog.stephencleary.com/2016/12/eliding-async-await.html. A few recent async-await bugs in our code probably just made me unreasonably suspicious to absent await's.
  • Stephen Cleary
    Stephen Cleary over 4 years
    @EugenePodskal: I believe there are some analyzers that can catch things like missing awaits.
  • Matt
    Matt over 3 years
    async: "There is no thread!" - awsome article. I like it!
  • Achilles P.
    Achilles P. over 2 years
    Isn't .ConfigureAwait(false) incorrect here? You actually do want to resume on the UI thread after the LoadContentAsync method finishes. Or did I totally misunderstand how this works?
  • Beltway
    Beltway over 2 years
    @StephenCleary Some minor detail I am missing from that is whether a method running a CPU bound Task async should itself await or just return that Task. I usually go for: return Task.Run(() => doCpuWork(), cancellationToken); for any ${SyncMethod}Async() implementation. Should I prefer return await Task.Run(() => doCpuWork(), cancellationToken); plus the async key word? I figured the caller is expected to await if appropriate anyway.
  • Stephen Cleary
    Stephen Cleary over 2 years
    @Beltway: See eliding async and await.
  • Theodor Zoulias
    Theodor Zoulias about 2 years
    Stephen do you still believe that the advice "Use ConfigureAwait(false) when you can." is a good advice, in the context of a question that asks about the use of Task.Run in a GUI application? If you do, could you please make a trivial edit in the answer because I want to reassess my vote? :-)
  • Stephen Cleary
    Stephen Cleary about 2 years
    @TheodorZoulias: My answer was twofold because both approaches are valid. Task.Run is good for offloading blocking/synchronous work, and ConfigureAwait(false) (applied to code outside the Task.Run, of course) prevents unnecessary continuations hitting the UI thread - what Stephen Toub refers to as the thousand paper cuts. I did run into this in my first async desktop app: sluggish behavior just due to too many continuations hitting the UI thread. Most apps that's not a problem, though.
  • Theodor Zoulias
    Theodor Zoulias about 2 years
    Stephen my objection is that this advice, although it has merits, is not directly related with the question asked. Here is a question where the advice would be relevant: Why would I bother to use Task.ConfigureAwait(false)? The advice is positioned prominently inside the question, long before any mention to the Task.Run, which is what the OP is interested for. So I don't think that it's a good answer honestly, and my vote (from May 17 '20) doesn't reflect well my current opinion.
  • Stephen Cleary
    Stephen Cleary about 2 years
    The intention of my answer is both/and, not either/or. Both are recommended; it's not two different answers.
  • Jeppe
    Jeppe about 2 years
    In the last snippet: await Task.Run(() => DoWorkAsync()); - what's the difference between that and await DoWorkAsync();?
  • Stephen Cleary
    Stephen Cleary about 2 years
    Task.Run begins executing the asynchronous method on a thread pool thread.