Смотрю пример кода. Удивило, что сначала 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 )
Дополнительное чтение по теме от того же Stephen Toub: ConfigureAwait FAQ (и русский перевод) — спасибо @aepot за наводку.