ConfigureAwait и GetAwaiter меняют поведение - PullRequest
1 голос
/ 11 марта 2019

Я экспериментирую с Задачами. У меня

private async Task<string> GetStringWithInnerCallConfigureAwaitFalseAsync()
{
    await Task.Delay(3000).ConfigureAwait(false);
    return "Finished!";
}

private async Task<string> GetStringAsync()
{
    await Task.Delay(3000);
    return "Finished!";
}

Что странно для меня:

private void Button10_Click(object sender, RoutedEventArgs e)
{
    Button10.Content = "GetAwaiter() GetResult() + deadlock";
    var task = GetStringAsync().ConfigureAwait(false).GetAwaiter();
    var result = task.GetResult(); // deadlock
    Button10.Content = result;
}

Я ожидал НЕТ тупика и сбой в последней строке из-за неинтерфейсного контекста, как это было бы, если бы я не использовал GetAwaiter (), но у меня возникла тупик

Далее:

private void Button11_Click(object sender, RoutedEventArgs e)
{
    Button11.Content = "GetAwaiter() GetResult() No deadlock";
    var task = GetStringWithInnerCallConfigureAwaitFalseAsync().ConfigureAwait(false).GetAwaiter();
    var result = task.GetResult(); // No deadlock
    Button11.Content = result; // No crash
}

Здесь я ожидал сбой в последней строке из-за не-пользовательского интерфейса, но он работает без проблем. Разве ConfigureAwait (false) не имеет смысла, когда мы используем GetAwaiter () ?

1 Ответ

3 голосов
/ 11 марта 2019

ConfigureAwait настраивает поведение ключевого слова await в том же выражении. Он не влияет на другие операторы await в других методах и не действует, если вы не используете await.

Он определяет, захватывает ли оператор await текущий SynchronizationContext (если он есть). На практике, если вы выполняете оператор await в потоке пользовательского интерфейса, await task; будет запускать код после await в потоке пользовательского интерфейса, тогда как await task.ConfigureAwait(false) будет запускать код после await в потоке ThreadPool.

В вашем первом примере:

private async Task<string> GetStringWithInnerCallConfigureAwaitFalseAsync()
{
    await Task.Delay(3000).ConfigureAwait(false);
    return "Finished!"; // <-- Run on the thread pool
}

private async Task<string> GetStringAsync()
{
    await Task.Delay(3000);
    return "Finished!"; // <-- Run on the captured SynchronizationContext (if any)
}

Здесь есть различие в поведении, и именно в этом потоке выполняется оператор return.

В первом методе, когда Task, являющийся awaited, завершается, сообщение отправляется в пул потоков , который выполняет оператор return.

Во втором методе оператор await захватывает текущий SynchronizationContext (который относится к потоку пользовательского интерфейса) и использует его для запуска оператора return. Это означает, что по прошествии 3 секунд сообщение отправляется в поток пользовательского интерфейса, сообщая ему о необходимости выполнения оператора return.


В вашем первом фрагменте, который вызывает GetStringAsync:

var task = GetStringAsync().ConfigureAwait(false).GetAwaiter();

Вызов ConfigureAwait здесь ничего не делает, потому что вы не await получаете результат. Вы можете удалить его без изменений.

var result = task.GetResult(); // deadlock

Поток пользовательского интерфейса необходим для запуска оператора return в GetStringAsync. Поскольку вы заблокировали его при вызове GetResult(), он не может завершить метод GetStringAsync, и поэтому у вас тупик.


Во втором фрагменте, который вызывает GetStringWithInnerCallConfigureAwaitFalseAsync:

var task = GetStringWithInnerCallConfigureAwaitFalseAsync().ConfigureAwait(false).GetAwaiter();

Опять же, вызов ConfigureAwait(false) ничего не делает, потому что вы не await получаете результат.

var result = task.GetResult(); // No deadlock

На этот раз GetStringWithInnerCallConfigureAwaitFalseAsync делает await ...ConfigureAwait(false), и поэтому код после await запускается в пуле потоков, а не в потоке пользовательского интерфейса. Поэтому поток пользовательского интерфейса не требуется для завершения этого метода, и поэтому вы можете безопасно (!) Заблокировать его.

Button11.Content = result; // No crash

Вы вызвали этот метод в потоке пользовательского интерфейса, и вы никогда его не перемещали - вы вызываете все синхронно, нет await с и т. Д. Поэтому вы все еще находитесь в потоке пользовательского интерфейса на данный момент

...