Не понимаю, как работает await и Task

Это код, вызывающий диалоговое окно в Avalonia:

public async Task<TResult> ShowDialogAsync<TResult>() 
{
    return await GetWindow().ShowDialog<TResult>(m_target); 
}

И он работает успешно. Я попытался избавиться от async-await:

public TResult ShowDialog<TResult>() 
{
   var result = GetWindow().ShowDialog<TResult>(m_target);
   return result.Result;
}

Но диалоговое окно не отрисовывается, и закрыть его становится невозможно. Видимо, происходит взаимная блокировка. И как это обойти?


Ответы (2 шт):

Автор решения: CrazyElf

А зачем вы хотите ухудшить код?

public async Task<TResult> ShowDialogAsync<TResult>() 
{
    return await GetWindow().ShowDialog<TResult>(m_target); 
}

В этом коде в момент вызова await поток освобождается и UI может обрабатывать события - отрисовывать окна, обрабатывать ввод пользователя, рисовать что-то, всё замечательно. Как только диалоговое окно закроется, будет получен и возвращён результат работы.

public TResult ShowDialog<TResult>() 
{
   var result = GetWindow().ShowDialog<TResult>(m_target);
   return result.Result;
}

Здесь поток блокируется в момент ожидания результата работы задачи result.Result, управление никуда не передаётся. Причём, видимо, это был поток UI, который в любой системе ровно один. Таким образом UI "висит", не может обрабатывать диалог, который вы хотели показать, программа "намертво повисла".

Асинхронность при работе с UI - это прекрасно, пользуйтесь ей, не нужно от неё избавляться.

Дополнение - вариант от aepot.

Если await - последний и единственный в методе, а также не находится в блоке try-catch или using, можно просто убрать и async и await не затронув поведение метода. Вы просто пробрасываете выше Task, который возвращает вызываемый в коде метод.

public Task<TResult> ShowDialogAsync<TResult>() 
{
    return GetWindow().ShowDialog<TResult>(m_target); 
}

Или, если использовать современную версию C# для однострочного кода:

public Task<TResult> ShowDialogAsync<TResult>() => GetWindow().ShowDialog<TResult>(m_target); 
→ Ссылка
Автор решения: Arkee

Оставлю тут:

Если присмотреться к исходнику: то при вызове ShowDialog формируется TaskCompletionSource, которая подписывается на событие закрытия формы и ждет его выполнения. А само открытие и формирование формы происходит в потоке UI.

Если вызвать Task.Result то произойдет блокировка вызывающего потока UI и форма не успеет отобразится, а событие закрытия окажется в подвешенном состоянии.

Как говорилось выше, без асинхронности здесь никак. Сама логика вызова диалогового окна подразумевает блокировку основного окна, вывода диалога, получения TResult/DialogResult при закрытии диалога и возврат управления на основное окно.

→ Ссылка