一个主要区别在于异常传播。异常,抛出在async Task
方法,存储在返回的Task
对象并保持休眠状态,直到通过观察任务await task
, task.Wait()
, task.Result
or task.GetAwaiter().GetResult()
。即使从同步的一部分async
method.
考虑以下代码,其中OneTestAsync
and AnotherTestAsync
行为完全不同:
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)
,它会产生以下输出:
Press enter to continue
Error: One or more errors occurred.await Task.Delay
Error: 2nd
Note, I had to press Enter to see it.
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 失败时。
附带说明一下,错误传播逻辑仍然不同async void
methods(相对于async Task
方法)。内部引发的异常async void
方法将立即在当前线程的同步上下文中重新抛出(通过SynchronizationContext.Post
),如果当前线程有一个 (SynchronizationContext.Current != null)
。否则,它将被重新抛出ThreadPool.QueueUserWorkItem
)。调用者没有机会在同一堆栈帧上处理此异常。
我发布了一些有关 TPL 异常处理行为的更多详细信息here and here.
Q:是否可以模仿异常传播行为async
非异步方法Task
-基于方法,以便后者不会抛出在同一个堆栈帧上?
A:如果确实需要,那么是的,有一个技巧:
// 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
仍然可以异步执行。
Another notable difference is that
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
}
改成非异步版本就不会死锁了:
Task TestAsync()
{
return Task.Delay(1000);
}
斯蒂芬·克利里 (Stephen Cleary) 在他的著作中很好地解释了死锁的本质。blog.