我发现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,然后是一两个,等等。
相反,请考虑以下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
。此外,可以通过从async
移除await
和CalculateNextAsync
来解决此问题:
private static Task<int> CalculateNextAsync()
{
return Task.Run(async () =>
{
await Task.Delay(2000);
return ++_i;
});
}
现在一切都与控制台应用程序中的工作方式相同。
有人可以解释控制台中的行为与UWP应用程序不同的原因吗?为什么在UWP应用程序中任务保持为c
?
我又回到了这个问题,但发现了最初接受的答案没有涵盖的问题 - UWP 上的代码永远不会到达 .Result
,它只是继续检查对于返回IsCompleted
的{{1}},将返回false
。是什么让_lastResult
在应该完成时具有Task
状态?
我发现原因是主动等待AwaitingActivation
循环阻止while
继续再次占用UI线程,从而导致类似“死锁”的情况。
答案 0 :(得分:7)
根据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());
}
}
你正在混合async / await和阻止UWP应用程序中的.Result
之类的调用,因为它有一个块SynchronizationContext导致死锁。控制台应用程序是该规则的一个例外,这就是它在那里工作而不是在UWP应用程序中的原因。
此死锁的根本原因是await处理的方式 上下文。默认情况下,等待未完成的任务时,当前 捕获“上下文”并用于在任务时恢复该方法 完成。这个“上下文”是当前的SynchronizationContext,除非 它是null,在这种情况下它是当前的TaskScheduler。 GUI和 ASP.NET应用程序具有仅允许的SynchronizationContext 一次运行一个代码块。当await完成时,它 尝试在。中执行其余的异步方法 捕获的上下文但是那个上下文已经有了一个线程,其中 是(同步)等待异步方法完成。他们是 每个人都在等着对方,造成僵局。
请注意,控制台应用程序不会导致此死锁。他们有个 线程池SynchronizationContext而不是一次一个块 SynchronizationContext,所以当await完成时,它会安排 线程池线程上的异步方法的剩余部分。方法是 能够完成,完成其返回的任务,而且没有 僵局。这种行为上的差异可能会令人困惑 程序员编写测试控制台程序,观察部分异步 代码按预期工作,然后将相同的代码移动到GUI或 ASP.NET应用程序,它死锁。