异步任务方法WaitingForActivation

时间:2017-10-27 02:50:48

标签: c# asynchronous async-await task task-parallel-library

我发现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移除awaitCalculateNextAsync来解决此问题:

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线程,从而导致类似“死锁”的情况。

1 个答案:

答案 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应用程序,它死锁。

参考Async/Await - Best Practices in Asynchronous Programming