【问题标题】:Async Task method WaitingForActivation异步任务方法 WaitingForActivation
【发布时间】:2018-04-08 13:42:25
【问题描述】:

我发现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 应用 sn-p:

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 sn-p 只是保持打印出 0,if 语句中的任务状态只是保持Waitingforactivation。此外,可以通过从CalculateNextAsync 中删除asyncawait 来解决此问题:

private static Task<int> CalculateNextAsync()
{
    return Task.Run(async () =>
    {
        await Task.Delay(2000);
        return ++_i;
    });
}

现在一切都与控制台应用程序中的一样。

有人可以解释为什么控制台中的行为与 UWP 应用程序不同的原因吗?为什么在 UWP 应用的情况下任务保持为c

更新

我又回到了这个问题,但发现了一个最初接受的答案没有涵盖的问题 - UWP 上的代码从未到达.Result,它只是不断检查@ 987654332@ 返回false,因此返回_lastResult。是什么让 Task 在本应完成时具有 AwaitingActivation 状态?

解决方案

我发现原因是主动等待 while 循环阻止了 await 延续再次占用 UI 线程,从而导致类似“死锁”的情况。

【问题讨论】:

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


    【解决方案1】:

    根据 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 应用程序中工作的原因。

    这个死锁的根本原因是由于等待处理的方式 上下文。默认情况下,当等待未完成的任务时,当前 “上下文”被捕获并用于在任务时恢复方法 完成。这个“上下文”是当前的 SynchronizationContext 除非 它是 null,在这种情况下它是当前的 TaskScheduler。图形用户界面和 ASP.NET 应用程序有一个 SynchronizationContext,它只允许 一次运行一大块代码。当等待完成时,它 尝试在 捕获的上下文。但是那个上下文中已经有一个线程,它 正在(同步地)等待异步方法完成。他们是 互相等待,造成死锁。

    请注意,控制台应用程序不会导致这种死锁。他们有个 线程池 SynchronizationContext 而不是一次一个块 SynchronizationContext,所以当等待完成时,它会调度 线程池线程上的 async 方法的其余部分。方法是 能够完成,它完成了它返回的任务,并且没有 僵局。这种行为差异可能会令人困惑 程序员编写一个测试控制台程序,观察部分异步 代码按预期工作,然后将相同的代码移动到 GUI 或 ASP.NET 应用程序,它在哪里死锁。

    参考Async/Await - Best Practices in Asynchronous Programming

    【讨论】:

    • 太棒了,非常感谢您的清晰解释!
    • 我又回到了这个问题,但发现了一个问题——UWP 上的代码从未到达.Result,它只是不断检查IsCompleted。这是为什么呢?
    • 哦,我现在明白了,原因是while 循环永远不允许继续占用 UI 线程,这使得它“基本上”死锁。
    猜你喜欢
    • 2019-12-28
    • 1970-01-01
    • 2014-04-15
    • 1970-01-01
    • 1970-01-01
    • 2015-03-08
    • 2015-09-11
    相关资源
    最近更新 更多