Смотрю пример кода. Удивило, что сначала 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;)
Комментариев нет:
Отправить комментарий