Mar*_*und 5 c# asynchronous task task-parallel-library async-await
我发现了一种非常混乱的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;
});
}
Run Code Online (Sandbox Code Playgroud)
正如预期的那样,在它发布之后,它首先打印出一堆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());
}
}
Run Code Online (Sandbox Code Playgroud)
虽然这两者在一个小细节上有所不同,但UWP片段只是保持打印0并且if语句中的任务状态保持不变Waitingforactivation.此外,该问题可被固定通过去除async和await从CalculateNextAsync:
private static Task<int> CalculateNextAsync()
{
return Task.Run(async () =>
{
await Task.Delay(2000);
return ++_i;
});
}
Run Code Online (Sandbox Code Playgroud)
现在一切都与Console应用程序中的相同.
有人可以解释控制台中的行为与UWP应用程序不同的原因吗?为什么任务保持c在UWP应用程序的情况下?
我有回到这个问题了,但发现了一个问题,最初接受的答案不包括-在UWP代码永远达不到的.Result,它只是不断检查IsCompleted返回false,因此_lastResult被返回.是什么让它成为一个应该完成Task的AwaitingActivation状态?
我发现原因是主动等待while循环阻止await继续再次占用UI线程,从而导致类似"死锁"的情况.
基于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());
}
}
Run Code Online (Sandbox Code Playgroud)
您正在混合async/await和阻塞调用,就像.Result在UWP应用程序中一样,由于它的一个块SynchronizationContext导致死锁.控制台应用程序是该规则的一个例外,这就是它在那里工作而不是在UWP应用程序中工作的原因.
此死锁的根本原因是await处理上下文的方式.默认情况下,当等待未完成的任务时,捕获当前的"上下文"并用于在任务完成时恢复该方法.这个"上下文"是当前的SynchronizationContext,除非它是null,在这种情况下它是当前的TaskScheduler.GUI和ASP.NET应用程序具有SynchronizationContext,一次只允许运行一个代码块.当await完成时,它会尝试在捕获的上下文中执行异步方法的剩余部分.但是该上下文已经有一个线程,它(同步)等待异步方法完成.他们每个人都在等着对方,造成僵局.
请注意,控制台应用程序不会导致此死锁.它们有一个线程池SynchronizationContext而不是一次一个块的SynchronizationContext,因此当await完成时,它会在线程池线程上调度async方法的其余部分.该方法能够完成,完成其返回的任务,并且没有死锁.当程序员编写测试控制台程序,观察部分异步代码按预期工作,然后将相同的代码移动到GUI或ASP.NET应用程序中时,这种行为差异可能会令人困惑.
| 归档时间: |
|
| 查看次数: |
831 次 |
| 最近记录: |