Фон
В рамках транзакции состояния1 между различными приложениями на клиенте (обмен сообщениями между процессами WCF) важно, чтобы в это время пользователь не изменял состояние приложения. Сегодня эта «блокировка» осуществляется с помощью модальных диалогов в два этапа. Сначала на несколько сотен миллисекунд открывается «невидимый»2 модальный диалог. Это блокирует взаимодействие с пользователем, а также останавливает выполнение метода до закрытия диалогового окна. Если транзакция все еще не завершена после тайм-аута, вместо этого мы показываем видимый модальный диалог прогресса.
Это невидимое модальное диалоговое окно вызывает у нас проблемы, которые, как я думал, можно решить, просто сделав это await Task.Delay(timoutBeforeProgressDialog)
3 + блокируя пользовательский ввод с помощью фильтр сообщений. Я думал, что это даст тот же эффект, что и отображение невидимого модального диалога в течение короткого промежутка времени. Однако, похоже, это не так. Если во время обратного вызова в транзакции мы показываем пользователю окно сообщения с просьбой сохранить его изменения, await Task.Delay(timoutBeforeProgressDialog)
просто продолжит работу после тайм-аута и выведет диалоговое окно прогресса над окном сообщения, блокируя пользовательский ввод. Этого не произойдет с невидимым модальным окном. Когда невидимый модальный режим закрывается тайм-аутом, он не будет продолжать выполняться до тех пор, пока не будет закрыт модальный диалог обратных вызовов.
Вы можете возразить, что так поступать не следует, а логику транзакций следует переделать. Эта логика, однако, очень укоренилась и широко распространена в приложении и, следовательно, является дорогостоящим рефакторингом. Я надеялся просто реорганизовать эту невидимую логику модального диалога.
Я мог бы также добавить, что исторически мы не показывали невидимое модальное окно в этой ситуации, вместо этого мы вызывали Application.DoEvents() в цикле, пока не были готовы продолжить (что дает тот же эффект). Все это было реализовано задолго до того, как async await пришел в .NET.
1 Транзакция включает в себя запрос на переключение контекста и последующую обработку обратных вызовов переключения контекста.
2 Невидимое модальное окно: просто модальное диалоговое окно, которое не отвлекает внимание, не имеет ширины или высоты и открывается за пределами экрана.
3 Существует также токен отмены, чтобы остановить задержку, если транзакция завершится раньше.
Вопрос
Я хочу остановить выполнение метода до тех пор, пока все модальные диалоги в приложении не будут закрыты (или, перефразируя: до тех пор, пока основной цикл сообщений не будет прокачан). Пример того, чего я пытаюсь достичь:
public partial class Form1 :Form
{
public Form1() {
InitializeComponent();
}
private async void button_Click(object sender, EventArgs e) {
this.BeginInvoke(new MethodInvoker(() => {
MessageBox.Show(this, "A modal dialog", "Dialog", MessageBoxButtons.OK, MessageBoxIcon.Information);
}));
await Task.Delay(1000);
// wait for all modals to be closed / the main message loop is running.
await AllModalsClosed();
Console.WriteLine("This should be logged after user presses OK in the dialog shown above.");
}
private async Task AllModalsClosed() {
var allModalsClosedSource = new TaskCompletionSource<bool>();
if (!this.TopLevelControl.CanFocus) {
// Modals are open
Application.LeaveThreadModal += (object s, EventArgs args) => {
allModalsClosedSource.SetResult(true);
};
await allModalsClosedSource.Task;
}
}
}
Но я не уверен, что это будет правильно во всех ситуациях или это лучший способ. Я также должен сделать эту реализацию независимой от фактического диалога, так как он может быть где угодно в приложении. Обратите внимание, что я не хочу блокировать основной поток.
Я также пытался исследовать, есть ли способ отправки с помощью BeginInvoke в основной цикл сообщений, если бы это было возможно, я мог бы переписать AllModalsClosed
на:
private async Task AllModalsClosed() {
var allModalsClosedSource = new TaskCompletionSource<bool>();
// Made up BeginInvoke variant.
// Is anything like this possible?
this.BeginInvokeToMainMessageLoop(new MethodInvoker(() => {
allModalsClosedSource.SetResult(true);
}));
await allModalsClosedSource.Task;
}
В качестве альтернативы, может быть, есть какой-то способ настроить задачу, чтобы она продолжала только основной цикл сообщений после завершения?
Task.Run
я создал модальные диалоговые окна в разных потоках. И спасибо за отзыв, не беспокойтесь, приложите достаточно усилий, чтобы проверить все решения. 13.12.2019