簡體   English   中英

異步任務方法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,然后是一些,兩個等等。

相反,請考慮以下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刪除asyncawait來解決此問題:

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

現在一切都與Console應用程序中的相同。

有人可以解釋控制台中的行為與UWP應用程序不同的原因嗎? 為什么在UWP應用程序中任務保持為c

更新

我再次回到這個問題,但發現一個問題,原來接受的答案沒有涵蓋 - UWP上的代碼永遠不會到達 .Result ,它只是繼續檢查返回false IsCompleted ,因此返回_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());
    }
}

原始答案

你是混合異步/等待和阻塞樣來電.Result在UWP的應用程序,這是造成,因為它的一個塊的SynchronizationContext的僵局。 控制台應用程序是該規則的一個例外,這就是它在那里工作而不是在UWP應用程序中工作的原因。

此死鎖的根本原因是await處理上下文的方式。 默認情況下,當等待未完成的任務時,捕獲當前的“上下文”並用於在任務完成時恢復該方法。 這個“上下文”是當前的SynchronizationContext,除非它是null,在這種情況下它是當前的TaskScheduler。 GUI和ASP.NET應用程序具有SynchronizationContext,一次只允許運行一個代碼塊。 當await完成時,它會嘗試在捕獲的上下文中執行異步方法的剩余部分。 但是該上下文已經有一個線程,它(同步)等待異步方法完成。 他們每個人都在等着對方,造成僵局。

請注意,控制台應用程序不會導致此死鎖。 它們有一個線程池SynchronizationContext而不是一次一個塊的SynchronizationContext,因此當await完成時,它會在線程池線程上調度async方法的其余部分。 該方法能夠完成,完成其返回的任務,並且沒有死鎖。 當程序員編寫測試控制台程序,觀察部分異步代碼按預期工作,然后將相同的代碼移動到GUI或ASP.NET應用程序中時,這種行為差異可能會令人困惑。

參考Async / Await - 異步編程的最佳實踐

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM