Async/Await action within Task.Run() - c#

Task.Run(()=>{}) puts the action delegate into the queue and returns the task .
Is there any benefit of having async/await within the Task.Run()?
I understand that Task.Run() is required since if we want to use await directly, then the calling method will need to be made Async and will affect the calling places.
Here is the sample code which has async await within Task.Run(). The full sample is provided here: Create pre-computed tasks.
Task.Run(async () => { await new WebClient().DownloadStringTaskAsync("");});
Alternatively this could have been done:
Task.Run(() => new WebClient().DownloadStringTaskAsync("").Result;);
Since both, Task.Run() and Await will queue the work and will be picked by the thread pool, could the async/await within the Task.Run() be a bit redundant?

Is there any benefit of having async/await within the Task.Run() ?
Yes. Task.Run runs some action on a thread-pool thread. If such action does some IO work and asynchronously waits for the IO operation to complete via await, then this thread-pool thread can be used by the system for other work while the IO operation is still running.
Example:
Task.Run( async () =>
{
DoSomeCPUIntensiveWork();
// While asynchronously waiting for this to complete,
// the thread is given back to the thread-pool
var io_result = await DoSomeIOOperation();
DoSomeOtherCPUIntensiveWork(io_result);
});

Is there any benefit of having async/await within the Task.Run()
An async method returns to the caller as soon as the first await is hit (that operates on a non-completed task). So if that first execution "streak" of an async method takes a long time Task.Run will alter behavior: It will cause the method to immediately return and execute that first "streak" on the thread-pool.
This is useful in UI scenarios because that way you can make 100% sure that you are not blocking the UI. Example: HttpWebRequestdoes DNS resolution synchronously even when you use one of the async methods (this is basically a library bug/design error). This can pause the UI thread. So you can use Task.Run to be 100% sure that the UI is never blocked for longer than a few microseconds.
So back to the original question: Why await inside a Task.Run body? For the same reason you normally await: To unblock the thread.

In the example that you linked the main thread is being blocked until the asynchronous operation is done. It's being blocked by calling Wait() (which by the way is generally a bad idea).
Let's have a look at the return from the DownloadStringAsync in the linked sample:
return Task.Run(async () =>
{
content = await new WebClient().DownloadStringTaskAsync(address);
cachedDownloads.TryAdd(address, content);
return content;
});
Why would you wrap this in a Task? Think about your options for a second. If you don't want to wrap this in a Task, how would you make sure the method returns a Task<string> and still have it work? You'd mark the method as async of course! However, if you mark your method as async and you call Wait on it, you'll most likely end up with a deadlock, since the main thread is waiting for the work to finish, and your blocking the main thread so it can't let you know it's done.
When marking a method as async, the state machine will run on the calling thread, in your example however, the state machine runs on a separate thread, meaning there is little to no work being done on the main thread.

Calling Async from Non-Async Methods
We do some stuff like that when we are trying to call an async method inside of a non-async method. Especially if the async method is a known quantity. We use more of a TaskFactory though ... fits a pattern, makes it easier to debug, makes sure everyone takes the same approach (and -- gives us one throat to choke if async-->sync starts acting buggy).
So, Your Example
Imagine, in your example, that you have a non-async function. And, within that function, you need to call await webClient.DoSomethingAsync(). You can't call await inside of a function that's not async -- the compiler won't let you.
Option 1: Zombie Infestation
Your first option is to crawl all the way back up your call stack, marking every da*n method along the way as async and adding awaits everywhere. Everywhere. Like, everywhere. Then -- since all those methods are now async, you need to make the methods that reference them all async.
This, btw, is probably the approach many of the SO enthusiasts are going to advocate. Because, "blocking a thread is a bad idea."
So. Yeah. This "let async be async" approach means your little library routine to get a json object just reached out and touched 80% of the code. Who's going to call the CTO and let him know?
Option 2: Just Go with the One Zombie
OR, you can encapsulate your async inside of some function like yours...
return Task.Run(async () => {
content = await new WebClient().DoSomethingAsync();
cachedDownloads.TryAdd(address, content);
return content;
});
Presto... the zombie infestation has been contained to a single section of code. I'll leave it to the bit-mechanics to argue over how/why that gets executed at the CPU-level. I don't really care. I care that nobody has to explain to the CTO why the entire library should now be 100% async (or something like that).

Confirmed, wrapping await with Task.Run use 2 threads instead of one.
Task.Run(async () => { //thread #1
await new WebClient().DownloadStringTaskAsync(""); //thread #2
});
Say you have four calls wrapped like this, it will use 4 x 2 = 8 threads.
It would be better to just call these with simple await instead. For example:
Task<byte[]> t1 = new WebClient().DownloadStringTaskAsync("");
Task<byte[]> t2 = new WebClient().DownloadStringTaskAsync("");
byte[] t1Result = await t1;
byte[] t2Result = await t2;
Here is the proof that wrapped Task.Run are using extra threads. (Not using WebClient to prove the point)
private static async Task wrapped()
{
List<Task> tasks = new List<Task>();
tasks.AddRange(new []
{
Task.Run(async() => await new MyThread().RunMe()),
Task.Run(async() => await new MyThread().RunMe()),
Task.Run(async() => await new MyThread().RunMe()),
Task.Run(async() => await new MyThread().RunMe()),
});
Thread.Sleep(1000);
int number = Process.GetCurrentProcess().Threads.Count;
Console.WriteLine($"While running thread count: {number}");
await Task.WhenAll(tasks);
}
Unwrapped
private static async Task unwrapped()
{
List<Task> tasks = new List<Task>();
Task<int> t1 = new MyThread().RunMe();
Task<int> t2 = new MyThread().RunMe();
Task<int> t3 = new MyThread().RunMe();
Task<int> t4 = new MyThread().RunMe();
tasks.AddRange(new[] {t1, t2, t3, t4});
Thread.Sleep(1000);
int number = Process.GetCurrentProcess().Threads.Count;
Console.WriteLine($"While running thread count: {number}");
int i1 = await t1;
int i2 = await t2;
int i3 = await t3;
int i4 = await t4;
}
Full POC code here
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncThreadDemo
{
class Program
{
static async Task Main(string[] args)
{
int number = Process.GetCurrentProcess().Threads.Count;
Console.WriteLine($"Init thread count: {number}");
//await wrapped();
await unwrapped();
number = Process.GetCurrentProcess().Threads.Count;
Console.WriteLine($"Done thread count: {number}");
Console.ReadLine();
}
private static async Task wrapped()
{
List<Task> tasks = new List<Task>();
tasks.AddRange(new []
{
Task.Run(async() => await new MyThread().RunMe()),
Task.Run(async() => await new MyThread().RunMe()),
Task.Run(async() => await new MyThread().RunMe()),
Task.Run(async() => await new MyThread().RunMe()),
});
Thread.Sleep(1000);
int number = Process.GetCurrentProcess().Threads.Count;
Console.WriteLine($"While running thread count: {number}");
await Task.WhenAll(tasks);
}
private static async Task unwrapped()
{
List<Task> tasks = new List<Task>();
Task<int> t1 = new MyThread().RunMe();
Task<int> t2 = new MyThread().RunMe();
Task<int> t3 = new MyThread().RunMe();
Task<int> t4 = new MyThread().RunMe();
tasks.AddRange(new[] {t1, t2, t3, t4});
Thread.Sleep(1000);
int number = Process.GetCurrentProcess().Threads.Count;
Console.WriteLine($"While running thread count: {number}");
int i1 = await t1;
int i2 = await t2;
int i3 = await t3;
int i4 = await t4;
}
}
public class MyThread
{
public static int _counter;
public async Task<int> RunMe()
{
await Task.Run(() =>
{
for (int i = 0; i < 2; ++i)
{
Thread.Sleep(1000);
Console.WriteLine($"T{Thread.CurrentThread.ManagedThreadId} {i}");
}
Console.WriteLine($"T{Thread.CurrentThread.ManagedThreadId} done");
});
return _counter++;
}
}
}

Related

Run two task asynchronously

I'm running a synchronous method. Inside it I have to run two big method, so I was thinking to run them asynchronously.. I was thinking something like
public void MyFunc()
{
var doWorkTask_1 = DoWork1();
var doWorkTask_2 = DoWork2();
var result1 = await doWorkTask_1;
var result2 = await doWorkTask_2;
if(result1 == result2)
....
Thread.Sleep(syncInterval);
}
To do this i need that:
DoWork1 and DoWork2 are asynchronous;
MyFunc is asynchrous too;
But no method is asynchronous!!!
SO I tried to do in another way:
public void MyFunc()
{
var doWorkTask_1 = Task.Run(() => DoWork1());
var doWorkTask_2 = Task.Run(() => DoWork2());
var result1 = doWorkTask_1.Result;
var result2 = doWorkTask_2.Result;
if(result1 == result2)
....
Thread.Sleep(syncInterval);
}
So, 1st question:
Do I have written same thing in two different ways?
2nd question. I have to run the MyFunc method every X time, so I call it in this way:
Task.Factory.StartNew(MyFunc);
Can I call it simply
MyFunc();
My question is because inside myFunc I have a Thread.Sleep. Can I let sleep the main thread or is better to let sleep a thread inside the main?
I hope I have been clear.
Thank you.
Have I written same thing in two different ways?
No. Your first method will execute two units of work in parallel, and will asynchronously wait on the first one, then the second one.
Your second method will execute two units of work in parallel, and will synchronously wait on the first one, then on the second one.
Can I let sleep the main thread or is better to let sleep a thread
inside the main?
That depends on what your application is doing. You could turn MyFunc to be async so you can use Task.Delay instead, which internally uses a timer and doesn't block (and you may also pass it a CancellationToken if needed):
public async Task MyFuncAsync()
{
// Do work
await Task.Delay(syncInterval);
}
Side note:
It seems to me like you may be using async over sync, which in general is a questionable approach. I would advise against it.
Instead, like in your first example, explicitly invoke Task.Run on these workers:
public async Task MyFuncAsync()
{
var firstTask = Task.Run(() => DoWork1());
var secondTask = Task.Run(() => DoWork2());
await Task.WhenAll(new[] { firstTask, secondTask });
await Task.Delay(syncInterval);
}
Use Task.WhenAll to create a new task, encapsulating both your worker tasks.
Creates a task that will complete when all of the supplied tasks have
completed.
https://msdn.microsoft.com/en-us/library/hh194874%28v=vs.110%29.aspx
public async void MyFunc()
{
var doWorkTask_1 = DoWork1();
var doWorkTask_2 = DoWork2();
var results = await Task.WhenAll(doWorkTask_1, doWorkTask_2);
}
If you can't do async all the way, and by asynchronous you mean that you want to process DoWork1 and DoWork2 concurrently on different threads then you can use Task.Run to offload the work to a different thread and Task.WaitAll to wait synchronously for both tasks to complete:
public void MyFunc()
{
var task1 = Task.Run(() => DoWork1());
var task2 = Task.Run(() => DoWork2());
Task.WaitAll(task1, task2);
if (task1.Result == task2.Result)
{
// ...
}
Thread.Sleep(syncInterval);
}
Now, since this uses 3 threads (two ThreadPool threads in Task.Run and the calling thread blocked on Task.WaitAll) when we only need 2 we can simplify and optimize the example by executing one of the operations on the calling thread:
public void MyFunc()
{
var task1 = Task.Run(() => DoWork1());
var result2 = DoWork2();
if (task1.Result == result2)
{
// ...
}
Thread.Sleep(syncInterval);
}

Is it OK to do some async/await inside some .NET Parallel.ForEach() code?

Given the following code, is it OK to do async/await inside a Parallel.ForEach ?
eg.
Parallel.ForEach(names, name =>
{
// Do some stuff...
var foo = await GetStuffFrom3rdPartyAsync(name);
// Do some more stuff, with the foo.
});
or is there some gotcha's that I need to be made aware of?
EDIT: No idea if this compiles, btw. Just Pseduo-code .. thinking out loud.
No, It doesn't make sense to combine async with Paralell.Foreach.
Consider the following example:
private void DoSomething()
{
var names = Enumerable.Range(0,10).Select(x=> "Somename" + x);
Parallel.ForEach(names, async(name) =>
{
await Task.Delay(1000);
Console.WriteLine("Name {0} completed",name);
});
Console.WriteLine("Parallel ForEach completed");
}
What output you will expect?
Name Somename3 completed
Name Somename8 completed
Name Somename4 completed
...
Parallel ForEach completed
That's not what will happen. It will output :
Parallel ForEach completed
Name Somename3 completed
Name Somename8 completed
Name Somename4 completed
...
Why? Because when ForEach hits first await the method actually returns, Parallel.ForEach doesn't know it is asynchronous and it ran to completion!. Code after await runs as continuation on another thread not "Paralell processing thread"
Stephen toub addressed this here
From the name, I'm assuming that GetStuffFrom3rdPartyAsync is I/O-bound. The Parallel class is specifically for CPU-bound code.
In the asynchronous world, you can start multiple tasks and then (asynchronously) wait for them all to complete using Task.WhenAll. Since you're starting with a sequence, it's probably easiest to project each element to an asynchronous operation, and then await all of those operations:
await Task.WhenAll(names.Select(async name =>
{
// Do some stuff...
var foo = await GetStuffFrom3rdPartyAsync(name);
// Do some more stuff, with the foo.
}));
A close alternative might be this:
static void ForEach<T>(IEnumerable<T> data, Func<T, Task> func)
{
var tasks = data.Select(item =>
Task.Run(() => func(item)));
Task.WaitAll(tasks.ToArray());
}
// ...
ForEach(names, name => GetStuffFrom3rdPartyAsync(name));
Ideally, you shouldn't be using a blocking call like Task.WaitAll, if you can make the whole chain of methods calls async, "all the way down" on the current call stack:
var tasks = data.Select(item =>
Task.Run(() => func(item)));
await Task.WhenAll(tasks.ToArray());
Furthermore, if you don't do any CPU-bound work inside GetStuffFrom3rdPartyAsync, Task.Run may be redundant:
var tasks = data.Select(item => func(item));
As pointed out by #Sriram Sakthivel there are some problems with using Parallel.ForEach with asynchronous lambdas. Steven Toub's ForEachASync can do the equivalent. He talks about it here, but here is the code:
public static class Extensions
{
public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
{
return Task.WhenAll(
from partition in Partitioner.Create(source).GetPartitions(dop)
select Task.Run(async delegate {
using (partition) while (partition.MoveNext()) await body(partition.Current);
}));
}
}
It uses the Partitioner class to create a load balancing partitioner(doco), and allows you to specify how many threads you want to run with the dop parameter. to see the difference between it and Parallel.ForEach. Try the following code.
class Program
{
public static async Task GetStuffParallelForEach()
{
var data = Enumerable.Range(1, 10);
Parallel.ForEach(data, async i =>
{
await Task.Delay(1000 * i);
Console.WriteLine(i);
});
}
public static async Task GetStuffForEachAsync()
{
var data = Enumerable.Range(1, 10);
await data.ForEachAsync(5, async i =>
{
await Task.Delay(1000 * i);
Console.WriteLine(i);
});
}
static void Main(string[] args)
{
//GetStuffParallelForEach().Wait(); // Finished printed before work is complete
GetStuffForEachAsync().Wait(); // Finished printed after all work is done
Console.WriteLine("Finished");
Console.ReadLine();
}
if you run GetStuffForEachAsync the program waits for all work to finish. If you run GetStuffParallelForEach, the line Finished will be printed before the work is finished.

The lack of non-capturing Task.Yield forces me to use Task.Run, why follow that?

Apologies in advance if this question is opinion-based. The lack of Task.Yield version which wouldn't capture the execution context was already discussed here. Apparently, this feature was present in some form in early versions of Async CTP but was removed because it could easily be misused.
IMO, such feature could be as easily misused as Task.Run itself. Here's what I mean. Imagine there's an awaitable SwitchContext.Yield API which schedules the continuation on ThreadPool, so the execution will always continues on a thread different from the calling thread. I could have used it in the following code, which starts some CPU-bound work from a UI thread. I would consider it a convenient way of continuing the CPU-bound work on a pool thread:
class Worker
{
static void Log(string format, params object[] args)
{
Debug.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, String.Format(format, args));
}
public async Task UIAction()
{
// UI Thread
Log("UIAction");
// start the CPU-bound work
var cts = new CancellationTokenSource(5000);
var workTask = DoWorkAsync(cts.Token);
// possibly await for some IO-bound work
await Task.Delay(1000);
Log("after Task.Delay");
// finally, get the result of the CPU-bound work
int c = await workTask;
Log("Result: {0}", c);
}
async Task<int> DoWorkAsync(CancellationToken ct)
{
// start on the UI thread
Log("DoWorkAsync");
// switch to a pool thread and yield back to the UI thread
await SwitchContext.Yield();
Log("after SwitchContext.Yield");
// continue on a pool thread
int c = 0;
while (!ct.IsCancellationRequested)
{
// do some CPU-bound work on a pool thread: counting cycles :)
c++;
// and use async/await too
await Task.Delay(50);
}
return c;
}
}
Now, without SwitchContext.Yield, DoWorkAsync would look like below. It adds some extra level of complexity in form of async delegate and task nesting:
async Task<int> DoWorkAsync(CancellationToken ct)
{
// start on the UI thread
Log("DoWorkAsync");
// Have to use async delegate
// Task.Run uwraps the inner Task<int> task
return await Task.Run(async () =>
{
// continue on a pool thread
Log("after Task.Yield");
int c = 0;
while (!ct.IsCancellationRequested)
{
// do some CPU-bound work on a pool thread: counting cycles :)
c++;
// and use async/await too
await Task.Delay(50);
}
return c;
});
}
That said, implementing SwitchContext.Yield may actually be quite simple and (I dare to say) efficient:
public static class SwitchContext
{
public static Awaiter Yield() { return new Awaiter(); }
public struct Awaiter : System.Runtime.CompilerServices.INotifyCompletion
{
public Awaiter GetAwaiter() { return this; }
public bool IsCompleted { get { return false; } }
public void OnCompleted(Action continuation)
{
ThreadPool.QueueUserWorkItem((state) => ((Action)state)(), continuation);
}
public void GetResult() { }
}
}
So, my question is, why should I prefer the second version of DoWorkAsync over the first one, and why would using SwitchContext.Yield be considered a bad practice?
You don't have to put the Task.Run in DoWorkAsync. Consider this option:
public async Task UIAction()
{
// UI Thread
Log("UIAction");
// start the CPU-bound work
var cts = new CancellationTokenSource(5000);
var workTask = Task.Run(() => DoWorkAsync(cts.Token));
// possibly await for some IO-bound work
await Task.Delay(1000);
Log("after Task.Delay");
// finally, get the result of the CPU-bound work
int c = await workTask;
Log("Result: {0}", c);
}
This results in code with much clearer intent. DoWorkAsync is a naturally synchronous method, so it has a synchronous signature. DoWorkAsync neither knows nor cares about the UI. The UIAction, which does care about the UI thread, pushes off the work onto a background thread using Task.Run.
As a general rule, try to "push" any Task.Run calls up out of your library methods as much as possible.

Why Task.Delay doesn`t work in this situation

I'm testing the async and I found this situation that I can't understand:
var watch = Stopwatch.StartNew();
var t1 = Task.Factory.StartNew(async () =>
{
await Task.Delay(2000);
return 2;
});
var t2 = Task.Factory.StartNew(() =>
{
Task.Delay(1000);
return 1;
});
await Task.WhenAll(t1, t2);
var result = watch.ElapsedMilliseconds;
I would like to understand why the result is always 0! Why is not 1000, 2000 or the sum of the two tasks 3000? Why doesn't Task.WhenAll wait for the completion of the tasks?
Okay, so, the second one is the easy one, so let's handle that one.
For the second task, t2, you don't do anything with the result of Task.Delay(1000). You don't await it, you don't Wait it, etc. Given that the method is not async I supposed you meant for it to be a blocking wait. To do that you'd want to add Wait() to the end of the Delay call to make it a blocking wait, or just use Thread.Sleep().
For the first task, you're being bitten by the fact that you're using var. It's clearer what's happening when you don't use var:
Task<Task<int>> t1 = Task.Factory.StartNew(async () =>
{
await Task.Delay(2000);
return 2;
});
You're returning a task of task of int, not just a Task of int. The outer task is "completed" as soon as the inner task finishes being started. When you use WhenAll you don't care about when the outer task finishes, you care about when the inner task finishes. There are a number of ways to handle this. One is to use Unwrap.
Task<int> t1 = Task.Factory.StartNew(async () =>
{
await Task.Delay(2000);
return 2;
}).Unwrap();
Now you have your expected Task<int> and WhenAll will take at least 2000 milliseconds, as expected. You could also add in another await call to do the same thing:
Task<int> t1 = await Task.Factory.StartNew(async () =>
{
await Task.Delay(2000);
return 2;
});
As mentioned by svick in a comment another option would be to just use Task.Run instead of StartNew. Task.Run has a special set of overloads for methods that take a Func<Task<T>> and return a Task<T> and automatically unwrap them for you:
Task<int> t1 = Task.Run(async () =>
{
await Task.Delay(2000);
return 2;
});
For this reason it's preferable to use Task.Run as the default option when you're creating async lambdas as it will "handle" this issue for you, although it's best to be aware of it for the complex cases where you can't use Task.Run.
Finally we come to the option that you didn't do, which is what you probably should be actually doing in this case. Since Task.Delay already returns a Task, there's no need to put it in StartNew in the first place. Rather than creating a nested task and using Unwrap you can just not wrap it in the first place:
var t3 = Task.Delay(3000);
await Task.WhenAll(t1, t2, t3);
If you actually just want to wait for a fixed amount of time that's what you should be doing.

Can the last await be replaced with an explicit wait?

I'm still learning the async/await, so please excuse me if I'm asking something obvious. Consider the following example:
class Program {
static void Main(string[] args) {
var result = FooAsync().Result;
Console.WriteLine(result);
}
static async Task<int> FooAsync() {
var t1 = Method1Async();
var t2 = Method2Async();
var result1 = await t1;
var result2 = await t2;
return result1 + result2;
}
static Task<int> Method1Async() {
return Task.Run(
() => {
Thread.Sleep(1000);
return 11;
}
);
}
static Task<int> Method2Async() {
return Task.Run(
() => {
Thread.Sleep(1000);
return 22;
}
);
}
}
This behaves as expected and prints "33" in the console.
If I replace the second await with an explicit wait...
static async Task<int> FooAsync() {
var t1 = Method1Async();
var t2 = Method2Async();
var result1 = await t1;
var result2 = t2.Result;
return result1 + result2;
}
...I seem to get the same behavior.
Are these two examples completely equivalent?
And if they are equivalent in this case, are there any other cases where replacing the last await by an explicit wait would make a difference?
Your replacement version blocks the calling thread waiting for the task to finish. It's hard to see a visible difference in a console app like that since you're intentionally blocking in Main, but they're definitely not equivalent.
They are not equivalent.
Task.Result blocks until the result is available. As I explain on my blog, this can cause deadlocks if you have an async context that requires exclusive access (e.g., a UI or ASP.NET app).
Also, Task.Result will wrap any exceptions in AggregateException, so error handling is harder if you synchronously block.
OK, I think I figured this out so let me sum it up, in what will hopefully be a more complete explanation than the answers provided so far...
Short Answer
Replacing the second await with an explicit wait will have no appreciable effect on a console application, but will block the UI thread of a WPF or WinForms application for the duration of the wait.
Also, the exception handling is slightly different (as noted by Stephen Cleary).
Long Answer
In a nutshell, the await does this:
If the awaited task has already finished, it just retrieves its result and continues.
If it hasn't, it posts the continuation (the rest of the method after the await) to the current synchronization context, if there is one. Essentially, await is trying to return us where we started from.
If there isn't a current context, it just uses the original TaskScheduler, which is usually thread pool.
The second (and third and so on...) await does the same.
Since the console applications typically have no synchronization context, continuations will typically be handled by the thread pool, so there is no issue if we block within the continuation.
WinForms or WPF, on the other hand, have synchronization context implemented on top of their message loop. Therefore, await executed on a UI thread will (eventually) execute its continuation on the UI thread as well. If we happen to block in the continuation, it will block the message loop and make the UI non-responsive until we unblock. OTOH, if we just await, it will neatly post continuations to be eventually executed on the UI thread, without ever blocking the UI thread.
In the following WinForms form, containing one button and one label, using await keeps the UI responsive at all times (note the async in front of the click handler):
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e) {
var result = await FooAsync();
label1.Text = result.ToString();
}
static async Task<int> FooAsync() {
var t1 = Method1Async();
var t2 = Method2Async();
var result1 = await t1;
var result2 = await t2;
return result1 + result2;
}
static Task<int> Method1Async() {
return Task.Run(
() => {
Thread.Sleep(3000);
return 11;
}
);
}
static Task<int> Method2Async() {
return Task.Run(
() => {
Thread.Sleep(5000);
return 22;
}
);
}
}
If we replaced the second await in FooAsync with t2.Result, it would continue to be responsive for about 3 seconds after the button click, and then freeze for about 2 seconds:
The continuation after the first await will politely wait its turn to be scheduled on the UI thread, which would happen after Method1Async() task finishes, i.e. after about 3 seconds,
at which point the t2.Result will rudely block the UI thread until the Method2Async() task finishes, about 2 seconds later.
If we removed the async in front of the button1_Click and replaced its await with FooAsync().Result it would deadlock:
The UI thread would wait on FooAsync() task to finish,
which would wait on its continuation to finish,
which would wait on the UI thread to become available,
which it isn't, since it is blocked by the FooAsync().Result.
The article "Await, SynchronizationContext, and Console Apps" by Stephen Toub was invaluable to me in understanding this subject.

Categories