async/await с параметром continueOnCapturedContext ConfigureAwait и SynchronizationContext для асинхронных продолжений
Я хотел бы сначала написать код, а затем объяснить ситуацию и задать свой вопрос на основании этого:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private async void Button_Click_2(object sender, RoutedEventArgs e) {
var result = await GetValuesAsync();
Foo.Text += result;
}
public async Task<string> GetValuesAsync() {
using (var httpClient = new HttpClient()) {
var response = await httpClient
.GetAsync("http://www.google.com")
.ConfigureAwait(continueOnCapturedContext: false);
// This is the continuation for the httpClient.GetAsync method.
// We shouldn't get back to sync context here
// Cuz the continueOnCapturedContext is set to *false*
// for the Task which is returned from httpClient.GetAsync method
var html = await GetStringAsync();
// This is the continuation for the GetStringAsync method.
// Should I get back to sync context here?
// Cuz the continueOnCapturedContext is set to *true*
// for the Task which is returned from GetStringAsync
// However, GetStringAsync may be executed in another thread
// which has no knowledge for the sync context
// because the continueOnCapturedContext is set to *false*
// for the Task which is returned from httpClient.GetAsync method.
// But, on the other hand, GetStringAsync method also has a
// chance to be executed in the UI thread but we shouldn't be
// relying on that.
html += "Hey...";
Foo.Text = html;
return html;
}
}
public async Task<string> GetStringAsync() {
await Task.Delay(1000);
return "Done...";
}
}
Это довольно простой пример WPF, который работает на.NET 4.5 и, вероятно, не имеет большого смысла, но это должно помочь мне объяснить мою ситуацию.
У меня есть кнопка на экране, которая имеет событие асинхронного нажатия. Когда вы смотрите на GetValuesAsync
код, вы увидите использование await
ключевое слово дважды. При первом использовании я установил continueOnCapturedContext
параметр Task.ConfigureAwait
метод для false
, Итак, это означает, что я не обязательно хочу, чтобы мое продолжение выполнялось внутри SynchronizationContext.Current
, Все идет нормально.
На втором await
использование (с GetStringAsync
метод), я не назвал ConfigureAwait
метод. Итак, я в основном указал, что хочу вернуться к текущему контексту синхронизации для продолжения GetStringAsync
метод. Итак, как вы можете видеть, я пытаюсь установить TextBlock.Text
(которое принадлежит UI-потоку) свойство внутри продолжения.
Когда я запускаю приложение и нажимаю кнопку, я получаю исключение, сообщающее мне следующее сообщение:
Вызывающий поток не может получить доступ к этому объекту, потому что другой поток владеет им.
Сначала это не имело смысла для меня, и я подумал, что обнаружил ошибку, но потом я понял, что GetStringAsync
может выполняться в другом потоке (весьма вероятно), который отличается от потока пользовательского интерфейса и не имеет сведений о контексте синхронизации, поскольку continueOnCapturedContext
установлен в false
для Task
который возвращается из httpClient.GetAsync
метод.
Это тот случай, здесь? Также, в этом случае, есть ли шанс для GetStringAsync
метод, который будет отправлен обратно в поток пользовательского интерфейса, поскольку продолжение метода httpClient.GetAsync может быть выполнено внутри потока пользовательского интерфейса?
У меня также есть несколько комментариев внутри кода. Учитывая мои вопросы и комментарии внутри кода, я что-то здесь упускаю?
1 ответ
Когда вы звоните ConfigureAwait(false)
остальная часть метода будет выполняться в потоке пула потоков, если только Task
Вы await
Инг уже завершен.
поскольку GetAsync
почти наверняка будет работать асинхронно GetStringAsync
работать в потоке пула потоков.
public async Task<string> GetValuesAsync() {
using (var httpClient = new HttpClient()) {
var response = await httpClient
.GetAsync("http://www.google.com")
.ConfigureAwait(continueOnCapturedContext: false);
// And now we're on the thread pool thread.
// This "await" will capture the current SynchronizationContext...
var html = await GetStringAsync();
// ... and resume it here.
// But it's not the UI SynchronizationContext.
// It's the ThreadPool SynchronizationContext.
// So we're back on a thread pool thread here.
// So this will raise an exception.
html += "Hey...";
Foo.Text = html;
return html;
}
}
Кроме того, в этом случае существует ли возможность отправки метода GetStringAsync обратно в поток пользовательского интерфейса, поскольку продолжение метода httpClient.GetAsync может выполняться внутри потока пользовательского интерфейса?
Единственный способ GetStringAsync
будет работать в потоке пользовательского интерфейса, если GetAsync
завершает, прежде чем это на самом деле await
редактор Весьма маловероятно.
По этой причине я предпочитаю использовать ConfigureAwait(false)
для каждого await
как только контекст больше не нужен.