I have been following this question and I understand the reasons behind the popular (albeit as-yet-unaccepted) answer by Peter Duniho. Specifically, I am aware that not awaiting a subsequent long-running operation will block the UI thread:
The second example does not yield during the asynchronous operation. Instead, by getting the value of the content.Result property, you force the current thread to wait until the asynchronous operation has completed.
I've even confirmed this, for my own benefit, like so:
private async void button1_Click(object sender, EventArgs e)
{
var value1 = await Task.Run(async () =>
{
await Task.Delay(5000);
return "Hello";
});
//NOTE: this one is not awaited...
var value2 = Task.Run(async () =>
{
await Task.Delay(5000);
return value1.Substring(0, 3);
});
System.Diagnostics.Debug.Print(value2.Result); //thus, UI freezes here after 5000 ms.
}
But now I'm wondering: do you need to await
all "awaitable" operations nested within an outermost awaitable operation? For example, I can do this:
private async void button1_Click(object sender, EventArgs e)
{
var value0 = await Task.Run(() =>
{
var value1 = new Func<Task<string>>(async () =>
{
await Task.Delay(5000);
return "hello";
}).Invoke();
var value2 = new Func<string, Task<string>>(async (string x) =>
{
await Task.Delay(5000);
return x.Substring(0, 3);
}).Invoke(value1.Result);
return value2;
});
System.Diagnostics.Debug.Print(value0);
}
Or I can do this:
private async void button1_Click(object sender, EventArgs e)
{
//This time the lambda is async...
var value0 = await Task.Run(async () =>
{
//we're awaiting here now...
var value1 = await new Func<Task<string>>(async () =>
{
await Task.Delay(5000);
return "hello";
}).Invoke();
//and we're awaiting here now, too...
var value2 = await new Func<string, Task<string>>(async (string x) =>
{
await Task.Delay(5000);
return x.Substring(0, 3);
}).Invoke(value1);
return value2;
});
System.Diagnostics.Debug.Print(value0);
}
And neither of them freeze the UI. Which one is preferable?
The last one is preferable (although quite messy)
In TAP (Task-based Asynchronous Pattern) A task (and other awaitables) represent an asynchronous operation. You have basically 3 options of handling these tasks:
DoAsync().Result
, DoAsync().Wait()
) - Blocks the calling thread until the task is completed. Makes your application more wasteful, less scalable, less responsive and susceptible to deadlocks. await DoAsync()
) - Doesn't block the calling thread. It basically registers the work after the await
as a continuation to be executed after the awaited task completes.DoAsync()
) - Doesn't block the calling thread, but also doesn't wait for the operation to complete. You are unaware of any exceptions thrown while DoAsync
is processedSpecifically, I am aware that not awaiting a subsequent long-running operation will block the UI thread
So, Not quite. If you don't wait at all, nothing will block but you can't know when or if the operation completed successfully. However, if you wait synchronously you will block the calling thread and you may have deadlocks if you're blocking the UI thread.
Conclusion: You should await
your awaitables as long as that's possible (it isn't in Main
for example). That includes "nested async-await
operations".
About your specific example: Task.Run
is used to offload CPU-bound work to a ThreadPool
thread, which doesn't seem to be what you are trying to mimic. If we use Task.Delay
to represent a truly asynchronous operation (usually I/O-bound) we can have "nested async-await
" without Task.Run
:
private async void button1_Click(object sender, EventArgs e)
{
var response = await SendAsync();
Debug.WriteLine(response);
}
async Task<Response> SendAsync()
{
await SendRequestAsync(new Request());
var response = await RecieveResponseAsync();
return response;
}
async Task SendRequestAsync(Request request)
{
await Task.Delay(1000); // actual I/O operation
}
async Task<Response> RecieveResponseAsync()
{
await Task.Delay(1000); // actual I/O operation
return null;
}
You can use anonymous delegates instead of methods, but it's uncomfortable when you need to define the types and invoke them yourself.
If you do need to offload that operation to a ThreadPool
thread, just add Task.Run
:
private async void button1_Click(object sender, EventArgs e)
{
var response = await Task.Run(() => SendAsync());
Debug.WriteLine(response);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With