Страницы

Поиск по вопросам

понедельник, 1 октября 2018 г.

Использование ConfigureAwait(false)

Смотрю пример кода. Удивило, что сначала ConfigureAwait(false) вызывается на httpClient.GetStringAsync, а затем на sourceStream.WriteAsync. Насколько я знаю ConfigureAwait(false) указывает, что код должен продолжать выполняться не в контексте UI, а в контексте таска. Зачем тогда 2 раза его вызывать?
private async void Button_Click(object sender, RoutedEventArgs e) { HttpClient httpClient = new HttpClient(); //до этого момента всё выполняется в UI контексте? string content = await httpClient.GetStringAsync("http://www.microsoft.com"). ConfigureAwait(false); //после выполнения верхней строчки остальной код который внизу будет выполняться в контексте веррхнего таска? using (FileStream sourceStream = new FileStream("temp.html", FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: true)) { byte[] encodedText = Encoding.Unicode.GetBytes(content); await sourceStream.WriteAsync(encodedText, 0, encodedText.Length). ConfigureAwait(false); //будь дальше какой-то код, в контексте какого потока он выполнялся б? }; }


Ответ

Смотрите.
ConfigureAwait(false) означает, и правда, «мне всё равно, в каком потоке SynchronizationContext'е будет выполняться хвост метода».
То есть первый ConfigureAwait(false) может отправить «хвост» метода в фоновый поток. Но именно что может, а не должен! Если по какой-то причине первый таск выполнится синхронно (например, строка есть уже в кэше), то перевод в другой SynchronizationContext осуществлён не будет, и выполнение будет продолжаться в исходном контексте.
Если при этом второй await не снабжён конструкцией ConfigureAwait(false), то хвост метода будет выполняться снова-таки в исходном контексте — то есть, в вашем случае в контексте UI.
Таким образом, для библиотечных методов, которые не общаются с UI, практически необходимо к каждому внутреннему await'у добавлять ConfigureAwait(false)

Понятно, что дописывать к каждому из await'ов ConfigureAwait(false) немного лень. Можно вместо этого использовать такой трюк: «сбежать» на пул потоков в самом начале, и не беспокоиться об этом больше. Это можно сделать при помощи такой конструкции:
private async void Button_Click(object sender, RoutedEventArgs e) { await AsyncHelper.RedirectToThreadPool(); // всё, мы больше не в UI-контексте, гарантировано
HttpClient httpClient = new HttpClient(); string content = await httpClient.GetStringAsync("http://www.microsoft.com"); // ... }
Вспомогательные классы (взяты отсюда):
static class AsyncHelper { public static ThreadPoolRedirector RedirectToThreadPool() => new ThreadPoolRedirector(); }
public struct ThreadPoolRedirector : INotifyCompletion { // awaiter и awaitable в одном флаконе public ThreadPoolRedirector GetAwaiter() => this;
// true означает выполнять продолжение немедленно public bool IsCompleted => Thread.CurrentThread.IsThreadPoolThread;
public void OnCompleted(Action continuation) => ThreadPool.QueueUserWorkItem(o => continuation());
public void GetResult() { } }
(идея взята из Stephen Toub await anything;)

Комментариев нет:

Отправить комментарий