之间&QUOT任何差异;等待Task.Run();返回;"和"返回Task.Run()"?
有code以下两片之间的任何概念上的差异:
Is there any conceptual difference between the following two pieces of code:
async Task TestAsync()
{
await Task.Run(() => DoSomeWork());
}
和
Task TestAsync()
{
return Task.Run(() => DoSomeWork());
}
是否产生code不同,要么?
Does the generated code differ, either?
编辑::要避免 Task.Run
混乱,类似的情况:
To avoid confusion with Task.Run
, a similar case:
async Task TestAsync()
{
await Task.Delay(1000);
}
和
Task TestAsync()
{
return Task.Delay(1000);
}
LATE更新:除了公认的答案,也有在 LocalCallContext
如何被处理的区别:的 CallContext.LogicalGetData得到即使没有异步恢复。为什么呢?
LATE UPDATE: In addition to the accepted answer, there is also a difference in how LocalCallContext
gets handled: CallContext.LogicalGetData gets restored even where there is no asynchrony. Why?
更新后,除了在异常传播行为的差异说明如下,还有另一种很微妙的区别:异步
/ 等待
版本更容易出现在非默认的同步上下文死锁。例如,下面将死锁一个WinForms或WPF应用程序:
Updated, besides the differences in the exception propagation behavior explained below, there's another somewhat subtle difference: the async
/await
version is more prone to dead-locking on a non-default synchronization context. E.g., the following will dead-lock in a WinForms or WPF application:
static async Task TestAsync()
{
await Task.Delay(1000);
}
void Form_Load(object sender, EventArgs e)
{
TestAsync().Wait(); // dead-lock here
}
将其更改为无异步版本,它不会死锁:
Change it to the non-async version and it won't dead-lock:
Task TestAsync()
{
return Task.Delay(1000);
}
死锁的性质由斯蒂芬·克利里在他的博客。
另一个主要区别是在异常传播。一个例外,一个
异步任务
方法中抛出,获取存储在返回的任务直到任务得到通过观察等待任务
对象,并保持休眠状态
, task.Wait()
, task.Result
或 task.GetAwaiter()调用getResult()
。它被传播,即使从抛出这种方式的同步的的的一部分异步
方法。
Another major difference is in exception propagation. An exception, thrown inside an
async Task
method, gets stored in the returned Task
object and remains dormant until the task gets observed via await task
, task.Wait()
, task.Result
or task.GetAwaiter().GetResult()
. It is propagated this way even if thrown from the synchronous part of the async
method.
考虑以下code,其中 OneTestAsync
和 AnotherTestAsync
表现完全不同:
Consider the following code, where OneTestAsync
and AnotherTestAsync
behave quite differently:
static async Task OneTestAsync(int n)
{
await Task.Delay(n);
}
static Task AnotherTestAsync(int n)
{
return Task.Delay(n);
}
// call DoTestAsync with either OneTestAsync or AnotherTestAsync as whatTest
static void DoTestAsync(Func<int, Task> whatTest, int n)
{
Task task = null;
try
{
// start the task
task = whatTest(n);
// do some other stuff,
// while the task is pending
Console.Write("Press enter to continue");
Console.ReadLine();
task.Wait();
}
catch (Exception ex)
{
Console.Write("Error: " + ex.Message);
}
}
如果我称之为 DoTestAsync(OneTestAsync,-2)
,它产生以下输出:
If I call DoTestAsync(OneTestAsync, -2)
, it produces the following output:
Press enter to continue
Error: One or more errors occurred.await Task.Delay
Error: 2nd
请注意,我不得不preSS 输入骨节病>看看吧。
Note, I had to press Enter to see it.
现在,如果我叫 DoTestAsync(AnotherTestAsync,-2)
,里面的 DoTestAsync的code的工作流程
是完全不同的,所以是输出。这个时候,我并没有被要求preSS 输入骨节病>:
Now, if I call DoTestAsync(AnotherTestAsync, -2)
, the code workflow inside DoTestAsync
is quite different, and so is the output. This time, I wasn't asked to press Enter:
Error: The value needs to be either -1 (signifying an infinite timeout), 0 or a positive integer.
Parameter name: millisecondsDelayError: 1st
在这两种情况下 Task.Delay(-2)
抛出伊始,在验证其参数。这可能是一个虚构的场景,但在理论上 Task.Delay(1000)
可能会抛出过,例如,当底层系统计时器API失败。
In both cases Task.Delay(-2)
throws at the beginning, while validating its parameters. This might be a made-up scenario, but in theory Task.Delay(1000)
may throw too, e.g., when the underlying system timer API fails.
在一个侧面说明,误差传播逻辑是没有不同的 异步无效
方法(而不是异步任务
方法)。内部的提出异步无效
方法将立即对当前线程的同步上下文重新抛出(通过 SynchronizationContext.Post $ C $例外C>),如果当前线程有一个(
SynchronizationContext.Current!= NULL)
。否则,将可通过 ThreadPool.QueueUserWorkItem
重新抛出)。主叫方没有机会处理同一堆栈帧此异常。
On a side note, the error propagation logic is yet different for async void
methods (as opposed to async Task
methods). An exception raised inside an async void
method will be immediately re-thrown on the the current thread's synchronization context (via SynchronizationContext.Post
), if the current thread has one (SynchronizationContext.Current != null)
. Otherwise, it will be re-thrown via ThreadPool.QueueUserWorkItem
). The caller doesn't have a chance to handle this exception on the same stack frame.
我张贴了关于太平人寿的异常处理行为 href=\"http://stackoverflow.com/a/22395161/1768303\">和的这里。
I posted some more details about TPL exception handling behaviour here and here.
问::是否有可能模仿的异常传播行为异步
非异步方法任务
为基础的方法,使后者不会抛出相同的堆栈帧?
Q: Is it possible to mimic the exception propagation behavior of async
methods for non-async Task
-based methods, so that the latter doesn't throw on the same stack frame?
A :如果真的需要,那么,有一个小窍门为:
A: If really needed, then yes, there is a trick for that:
// async
async Task<int> MethodAsync(int arg)
{
if (arg < 0)
throw new ArgumentException("arg");
// ...
return 42 + arg;
}
// non-async
Task<int> MethodAsync(int arg)
{
var task = new Task<int>(() =>
{
if (arg < 0)
throw new ArgumentException("arg");
// ...
return 42 + arg;
});
task.RunSynchronously(TaskScheduler.Default);
return task;
}
不过请注意,在一定条件下(比如当它太深堆栈上), RunSynchronously
仍然可以执行异步。
Note however, under certain conditions (like when it's too deep on the stack), RunSynchronously
could still execute asynchronously.