Why does packaging of Task.Run appear to cause different await behavior?
-
2012年8月21日 下午 05:06
Occasionally I write a piece of code using the the TAP pattern and an "await" does not appear to work. Here's an example of a small piece of code that works:
private async void button1_Click(object sender, EventArgs e) {
await Task.Run(async () => {
await Task.Delay(3000);
});
button1.Text = "Back";
}This works as expected, the button's text is not changed to "Back" for about three seconds. If I put my Task.Run in a separate method, though, it does not work:
private async void button1_Click(object sender, EventArgs e) {
await Task.Run(() => { Doit();});
button1.Text = "Back";
}
private async Task Doit() {
await Task.Delay(3000);
}In this case, the button's text is changed to "Back" immediately. It seems like effectively both pieces of code are the same, but apparently there's some difference.
What am I missing??
所有回覆
-
2012年8月21日 下午 05:41版主
In your second case, you're calling Task.Run, and it's running something that returns a Task - like so:
private async void button1_Click(object sender, EventArgs e) { await Task.Run(() => { // Doit runs and returns a task... Task task = Doit(); // This returns immediately because // you're never waiting or // using "await" on this task! }); button1.Text = "Back"; }
Since Doit() returns a Task, you don't need Task.Run - just await it directly:
private async void button1_Click(object sender, EventArgs e) { await Doit(); button1.Text = "Back"; }
Reed Copsey, Jr. - http://reedcopsey.com
If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".- 已標示為解答 Veloz 2012年8月21日 下午 05:44
-
2012年8月21日 下午 05:48
Ah, yes, I see your point. In the second example I have two Tasks being generated, the one from DoIt() and the one from Task.Run in button1_Click. And since the inner one isn't being awaited on, control immediately returns. Thanks for pointing that out.
I like the TAP pattern very much overall (it has already solved some real problems for me) but sometimes I get myself confused when introducing Task.Run and lambdas, etc.
-
2012年8月21日 下午 06:00版主
Ah, yes, I see your point. In the second example I have two Tasks being generated, the one from DoIt() and the one from Task.Run in button1_Click. And since the inner one isn't being awaited on, control immediately returns. Thanks for pointing that out.
I like the TAP pattern very much overall (it has already solved some real problems for me) but sometimes I get myself confused when introducing Task.Run and lambdas, etc.
In general, you probably only need Task.Run if you're wrapping code that isn't already asynchronous (ie: that doesn't return Task or Task<T>). If you're using a method that returns a Task, you won't need Task.Run.
Reed Copsey, Jr. - http://reedcopsey.com
If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful". -
2012年8月21日 下午 06:51
In general, you probably only need Task.Run if you're wrapping code that isn't already asynchronous (ie: that doesn't return Task or Task<T>). If you're using a method that returns a Task, you won't need Task.Run.
Yes, I'm seeing that the more I go. In a current project, I'm wrapping up SSH operations in a task so I can await them asynchronously (the library writer has no async equivalent)
Task DoSSHOperationAsync() {
return Task.Run(()=> { ssh operation; });
}Then I can simply await this operation:
await DoSSHOperationAsync();
Where I often get confused, is when writing methods like DoSSHOperationAsync, I find that I need/want to call other operations that are async. Let's say I need a small delay for example, somewhere in DoSSHOperationAsync and thus I want to use Task.Delay. My options seem to be Task.Delay.Wait() which doesn't seem to be the async-friendly way to go, or "await Task.Delay()". If I use the latter, I now need to mark DoSSHOperationAsync as "async Task", instead of just Task, and that's when the confusion starts.
Let's say I just leave the current code alone and add the async keyword and the delay.
async Task DoSSHOperationAsync() {
return Task.Run(()=> { await Task.Delay(); ssh operation;});
}Now I think I have a method that returns a task in a task, right? And this can't be very useful, lol.
So my first instinct is to say, well, "async" is returning a task for me, so I no longer need the Task.Run, so let's remove it. But of course if I do that then my ssh operation becomes sync again.
It's this "crossover" between using Task.Run and the TAP pattern that I'm stumbling on these days. Any insights whatsoever would be greatly appreciated :)
-
2012年8月21日 下午 07:14
Okay, I think I realized my own answer. In the example above, you can use await in a task created by Task.Run, as in:
Task DoSSHOperationAsync() {
return Task.Run(async ()=> {
await SomeOtherAsyncOperation;
ssh operation; });
}
This does not require me to mark the overall method as 'async'. That's what was messing me up.

