Обработчик кнопки WPF навсегда зависает на .Result. Найдите и исправьте взаимоблокировку.
Обработчик клика WPF вызывает асинхронный метод и блокируется на возвращённом Task через .Result. Окно зависает навсегда: продолжение внутри FetchAsync не выполняется, исключение не бросается, загрузка процессора нулевая.
Ограничения: обработчик по-прежнему должен присвоить полученный текст в Output.Text на потоке UI, а FetchAsync должен остаться async. Считайте, что SynchronizationContext.Current в обработчике клика — контекст диспетчера WPF.
private void OnClick(object sender, RoutedEventArgs e)
{
string data = FetchAsync().Result; // здесь UI зависает
Output.Text = data;
}
private async Task<string> FetchAsync()
{
using var http = new HttpClient();
string body = await http.GetStringAsync("https://example.com/data");
return body.Trim();
}
Найдите и исправьте ошибку.
.Result блокирует поток UI, а await в FetchAsync захватил SynchronizationContext диспетчера и отправляет продолжение в этот же заблокированный цикл. Лечится сквозной асинхронностью: ожидайте задачу через await в async-методе.
- ✗Винить пул потоков вместо захваченного
SynchronizationContext, в который отправляется продолжение - ✗Менять
.Resultна.Wait()илиGetAwaiter().GetResult()— все три блокируют тот же поток - ✗Ставить
ConfigureAwait(false)в обработчике, а не наawaitвнутри асинхронного метода
- →Почему
ConfigureAwait(false)внутриFetchAsyncтоже расклинил бы это, и почему такое решение всё же слабее? - →Почему тот же код не даёт взаимоблокировки в контроллере ASP.NET Core?
Баг
.Result синхронно блокирует поток UI. Но await внутри FetchAsync уже захватил SynchronizationContext диспетчера WPF и, когда HTTP-запрос завершится, попытается выполнить продолжение (body.Trim() и возврат значения) в цикле сообщений UI.
Цикл сообщений в этот момент стоит в .Result и не может обработать ни одного сообщения. Продолжение никогда не выполнится → задача никогда не завершится → .Result не вернётся. Классическая взаимоблокировка на двоих: поток UI ждёт задачу, задача ждёт поток UI.
private void OnClick(object sender, RoutedEventArgs e)
{
string data = FetchAsync().Result; // ❌ блокирует поток UI и его цикл сообщений
Output.Text = data;
}
private async Task<string> FetchAsync()
{
using var http = new HttpClient();
// ❌ await захватывает SynchronizationContext диспетчера
string body = await http.GetStringAsync("https://example.com/data");
return body.Trim(); // ❌ это продолжение никогда не запустится
}
Исправление
Асинхронность должна быть сквозной. Обработчик события — единственное законное место для async void: сделайте его async и ожидайте задачу через await вместо блокировки.
private async void OnClick(object sender, RoutedEventArgs e) // ✅ async void допустим для обработчика
{
string data = await FetchAsync(); // ✅ поток UI освобождается и продолжает качать сообщения
Output.Text = data; // ✅ продолжение возвращается в контекст диспетчера
}
Обработчик уходит с потока UI на время запроса, цикл сообщений продолжает работать, и когда задача завершается, продолжение спокойно отправляется в диспетчер — присвоение Output.Text идёт на потоке UI.
⚠️ Замена .Result на .Wait() или GetAwaiter().GetResult() ничего не лечит: все три блокируют тот же поток. ConfigureAwait(false) на await внутри FetchAsync тоже расклинит этот конкретный вызов (продолжение уйдёт на поток пула), но это лишь заплатка на стороне библиотеки — блокирующий вызывающий код всё равно занимает поток UI и остаётся ошибкой.