异步任务方法 WaitingForActivation
Async Task method WaitingForActivation
我发现 async
方法有一个非常令人困惑的行为。考虑以下控制台应用程序:
private static int _i = 0;
private static Task<int> _calculateTask = Task.FromResult(0);
private static int _lastResult = 0;
static void Main(string[] args)
{
while (true)
{
Console.WriteLine(Calculate());
}
}
private static int Calculate()
{
if (!_calculateTask.IsCompleted)
{
return _lastResult;
}
_lastResult = _calculateTask.Result;
_calculateTask = CalculateNextAsync();
return _lastResult;
}
private static async Task<int> CalculateNextAsync()
{
return await Task.Run(() =>
{
Thread.Sleep(2000);
return ++_i;
});
}
不出所料,启动后首先打印出一堆0,然后是1,2等等。
相比之下,请考虑以下 UWP 应用代码段:
private static int _i = 0;
private static Task<int> _calculateTask = Task.FromResult(0);
private static int _lastResult = 0;
public int Calculate()
{
if (!_calculateTask.IsCompleted)
{
return _lastResult;
}
_lastResult = _calculateTask.Result;
_calculateTask = CalculateNextAsync();
return _lastResult;
}
private static async Task<int> CalculateNextAsync()
{
return await Task.Run( async() =>
{
await Task.Delay(2000);
return ++_i;
});
}
private void Button_Click(object sender, RoutedEventArgs e)
{
while( true)
{
Debug.WriteLine(Calculate());
}
}
尽管这两者仅在一个小细节上有所不同,但 UWP 片段只是一直打印出 0,if
语句中的任务状态只是保持 Waitingforactivation
。此外,可以通过从 CalculateNextAsync
:
中删除 async
和 await
来解决该问题
private static Task<int> CalculateNextAsync()
{
return Task.Run(async () =>
{
await Task.Delay(2000);
return ++_i;
});
}
现在一切都与控制台应用程序中的工作方式相同。
有人可以解释控制台中的行为与 UWP 应用程序不同的原因吗?为什么在 UWP 应用程序中任务保持 c
?
更新
我又回到了这个问题,但发现了一个问题,原来接受的答案没有涵盖——UWP上的代码从来没有达到 .Result
,它只是不断检查 IsCompleted
和 returns false
,因此返回 _lastResult
。是什么让 Task
在本应完成时出现 AwaitingActivation
状态?
解决方案
我发现原因是活动等待 while
循环阻止 await
继续再次占用 UI 线程,因此导致 "deadlock" -类似的情况。
根据 UWP 应用程序中的代码,无需保留 _calculateTask
。只是 await
任务。
这是更新后的代码
private static int _i = 0;
private static int _lastResult = 0;
public async Task<int> Calculate() {
_lastResult = await CalculateNextAsync();
return _lastResult;
}
//No need to wrap the code in a Task.Run. Just await the async code
private static async Task<int> CalculateNextAsync()
await Task.Delay(2000);
return ++_i;
}
//Event Handlers allow for async void
private async void Button_Click(object sender, RoutedEventArgs e) {
while( true) {
var result = await Calculate();
Debug.WriteLine(result.ToString());
}
}
原始答案
您在 UWP 应用程序中混合 async/await 和阻止调用,如 .Result
,由于它的一个块 SynchronizationContext 而导致死锁。控制台应用程序是该规则的一个例外,这就是它在那里工作而不在 UWP 应用程序中工作的原因。
The root cause of this deadlock is due to the way await handles
contexts. By default, when an incomplete Task is awaited, the current
“context” is captured and used to resume the method when the Task
completes. This “context” is the current SynchronizationContext unless
it’s null, in which case it’s the current TaskScheduler. GUI and
ASP.NET applications have a SynchronizationContext that permits only
one chunk of code to run at a time. When the await completes, it
attempts to execute the remainder of the async method within the
captured context. But that context already has a thread in it, which
is (synchronously) waiting for the async method to complete. They’re
each waiting for the other, causing a deadlock.
Note that console applications don’t cause this deadlock. They have a
thread pool SynchronizationContext instead of a one-chunk-at-a-time
SynchronizationContext, so when the await completes, it schedules the
remainder of the async method on a thread pool thread. The method is
able to complete, which completes its returned task, and there’s no
deadlock. This difference in behavior can be confusing when
programmers write a test console program, observe the partially async
code work as expected, and then move the same code into a GUI or
ASP.NET application, where it deadlocks.
我发现 async
方法有一个非常令人困惑的行为。考虑以下控制台应用程序:
private static int _i = 0;
private static Task<int> _calculateTask = Task.FromResult(0);
private static int _lastResult = 0;
static void Main(string[] args)
{
while (true)
{
Console.WriteLine(Calculate());
}
}
private static int Calculate()
{
if (!_calculateTask.IsCompleted)
{
return _lastResult;
}
_lastResult = _calculateTask.Result;
_calculateTask = CalculateNextAsync();
return _lastResult;
}
private static async Task<int> CalculateNextAsync()
{
return await Task.Run(() =>
{
Thread.Sleep(2000);
return ++_i;
});
}
不出所料,启动后首先打印出一堆0,然后是1,2等等。
相比之下,请考虑以下 UWP 应用代码段:
private static int _i = 0;
private static Task<int> _calculateTask = Task.FromResult(0);
private static int _lastResult = 0;
public int Calculate()
{
if (!_calculateTask.IsCompleted)
{
return _lastResult;
}
_lastResult = _calculateTask.Result;
_calculateTask = CalculateNextAsync();
return _lastResult;
}
private static async Task<int> CalculateNextAsync()
{
return await Task.Run( async() =>
{
await Task.Delay(2000);
return ++_i;
});
}
private void Button_Click(object sender, RoutedEventArgs e)
{
while( true)
{
Debug.WriteLine(Calculate());
}
}
尽管这两者仅在一个小细节上有所不同,但 UWP 片段只是一直打印出 0,if
语句中的任务状态只是保持 Waitingforactivation
。此外,可以通过从 CalculateNextAsync
:
async
和 await
来解决该问题
private static Task<int> CalculateNextAsync()
{
return Task.Run(async () =>
{
await Task.Delay(2000);
return ++_i;
});
}
现在一切都与控制台应用程序中的工作方式相同。
有人可以解释控制台中的行为与 UWP 应用程序不同的原因吗?为什么在 UWP 应用程序中任务保持 c
?
更新
我又回到了这个问题,但发现了一个问题,原来接受的答案没有涵盖——UWP上的代码从来没有达到 .Result
,它只是不断检查 IsCompleted
和 returns false
,因此返回 _lastResult
。是什么让 Task
在本应完成时出现 AwaitingActivation
状态?
解决方案
我发现原因是活动等待 while
循环阻止 await
继续再次占用 UI 线程,因此导致 "deadlock" -类似的情况。
根据 UWP 应用程序中的代码,无需保留 _calculateTask
。只是 await
任务。
这是更新后的代码
private static int _i = 0;
private static int _lastResult = 0;
public async Task<int> Calculate() {
_lastResult = await CalculateNextAsync();
return _lastResult;
}
//No need to wrap the code in a Task.Run. Just await the async code
private static async Task<int> CalculateNextAsync()
await Task.Delay(2000);
return ++_i;
}
//Event Handlers allow for async void
private async void Button_Click(object sender, RoutedEventArgs e) {
while( true) {
var result = await Calculate();
Debug.WriteLine(result.ToString());
}
}
原始答案
您在 UWP 应用程序中混合 async/await 和阻止调用,如 .Result
,由于它的一个块 SynchronizationContext 而导致死锁。控制台应用程序是该规则的一个例外,这就是它在那里工作而不在 UWP 应用程序中工作的原因。
The root cause of this deadlock is due to the way await handles contexts. By default, when an incomplete Task is awaited, the current “context” is captured and used to resume the method when the Task completes. This “context” is the current SynchronizationContext unless it’s null, in which case it’s the current TaskScheduler. GUI and ASP.NET applications have a SynchronizationContext that permits only one chunk of code to run at a time. When the await completes, it attempts to execute the remainder of the async method within the captured context. But that context already has a thread in it, which is (synchronously) waiting for the async method to complete. They’re each waiting for the other, causing a deadlock.
Note that console applications don’t cause this deadlock. They have a thread pool SynchronizationContext instead of a one-chunk-at-a-time SynchronizationContext, so when the await completes, it schedules the remainder of the async method on a thread pool thread. The method is able to complete, which completes its returned task, and there’s no deadlock. This difference in behavior can be confusing when programmers write a test console program, observe the partially async code work as expected, and then move the same code into a GUI or ASP.NET application, where it deadlocks.