Button.PerformClick () не ждет обработчика асинхронных событий

У меня проблема в приложении Windows Forms, где я использую PerformClick для вызова обработчика событий async. Кажется, что обработчик событий не await, а просто немедленно возвращается. Я создал это простое приложение, чтобы показать проблему (это просто форма с 3 кнопками, которую легко проверить самостоятельно):

string message = "Not Started";

private async void button1_Click(object sender, EventArgs e)
{
    await MyMethodAsync();
}

private void button2_Click(object sender, EventArgs e)
{
    button1.PerformClick();
    MessageBox.Show(message);
}

private void button3_Click(object sender, EventArgs e)
{
    MessageBox.Show(message);
}

private async Task MyMethodAsync()
{
    message = "Started";
    await Task.Delay(2000);
    message = "Finished";
}

Интересная проблема здесь в том, что, по вашему мнению, показывает message, когда я нажимаю Button2? Как ни странно, показывает «Начато», а не «Завершено», как и следовало ожидать. Другими словами, это не await MyMethod(), он просто запускает задачу, а затем продолжает.

Редактировать:

В этом простом коде я могу заставить его работать, вызвав await Method() непосредственно из обработчика событий Button2, например:

private async void button2_Click(object sender, EventArgs e)
{
    await MyMethodAsync();
    MessageBox.Show(message);
}

Теперь он ждет 2 секунды и отображает «Готово».

Что здесь происходит? Почему не работает при использовании PerformClick?

Вывод:

Хорошо, теперь я понял, вывод такой:

  1. Никогда не вызывайте PerformClick, если обработчик событий - async. Это не будет await!

  2. Никогда не вызывайте обработчик событий async напрямую. Это не будет await!

Осталось отсутствие документации по этому поводу:

  1. Button.PerformClick должен иметь Предупреждение на странице документа:

Button.PerformClick «Вызов PerformClick не будет ожидать асинхронных обработчиков событий.»

  1. Вызов метода async void (или обработчика событий) должен дать компилятору Предупреждение: "Вы вызываете метод async void, он не будет ожидаться!"

вызовите экзорциста!

γηράσκω δ' αεί πολλά διδασκόμε 04.09.2018 00:02

У вас Конечно все еще говорит "Запущено" через 2 секунды? если вы продолжаете нажимать на свой button2, это означает, что сообщение меняется на «Запущено» в течение 2 секунд, прежде чем оно снова изменится на «Готово».

Saeb Amini 04.09.2018 00:27

В том-то и дело, что это не ожидание.

Poul Bak 04.09.2018 00:40

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

Saeb Amini 04.09.2018 00:49

Он работает как задумано. При вызове await MyMethodAsync(); система проверяет, вернулся ли MyMethodAsync. Это не так из-за Task.Delay(2000);, поэтому он возвращается к button2_Click и печатает "Начал". Разумеется, message = "Started"; уже выполнен.

γηράσκω δ' αεί πολλά διδασκόμε 04.09.2018 01:00

Вы ошибаетесь, смысл «ожидания» метода в том, что он не должен двигаться дальше, пока ожидаемый метод не вернется.

Poul Bak 04.09.2018 01:12

Вы можете отправить его в Microsoft как ошибку, чтобы исправить это.

γηράσκω δ' αεί πολλά διδασκόμε 04.09.2018 01:25

@PoulBak И button1_Click не уходит, пока не вернется MyMethodAsync. Но button2_Click не ожидает (и не может) ждать button1_Click, поэтому продолжает двигаться дальше.

user743382 04.09.2018 09:53

Ничего необычного здесь не происходит. Правильная ментальная модель состоит в том, что код после, выражение ожидания, выполняется позже, как если бы он был активирован таймером. Нет никакого механизма для блокировки метода в потоке пользовательского интерфейса, кроме ShowDialog () и ужасного Application.DoEvents (). Async / await, которого страшно из-за высокой вероятности возникновения неприятных ошибок повторного входа, не решает эту фундаментальную проблему.

Hans Passant 04.09.2018 10:35

@PoulBak я ожидал, что код покажет Started, а через две секунды он покажет Finished. Однако опубликованный вами код не позволяет вам увидеть этот переход. Он показывает содержимое поле, когда вы нажимаете кнопку. Если щелкнуть достаточно быстро, вы увидите Started. Если вы хотите, например, в течение 5 секунд, вы увидите Finished. Вместо того, чтобы сохранять значение в поле, попробуйте изменить заголовок формы. Здесь нет ошибки или чего-то неожиданного

Panagiotis Kanavos 04.09.2018 10:58
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
10
897
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Похоже, у вас есть некоторые неправильные представления о том, как работают async/await и / или PerformClick(). Чтобы проиллюстрировать проблему, рассмотрим следующий код:

private async Task MyMethodAsync()
{
    await Task.Delay(2000);
    message = "Finished";      // The execution of this line will be delayed by 2 seconds.
}

private void button2_Click(object sender, EventArgs e)
{
    message = "Started";
    MyMethodAsync();           // Since this method is not awaited,
    MessageBox.Show(message);  // the execution of this line will NOT be delayed.
}

Note: the compiler will give us a warning but let's ignore that for the sake of testing.

Итак, что вы ожидаете от MessageBox? Вы, наверное, сказали бы "началось" .1 Почему? Потому что мы не ждали метода MyMethodAsync(); код внутри этого метода запускает асинхронно, но мы не сделали ждать для его завершения, мы просто перешли к следующей строке, где значение message еще не изменилось.

Если вы до сих пор понимаете такое поведение, остальное должно быть легко. Итак, давайте немного изменим приведенный выше код:

private async void button1_Click(object sender, EventArgs e)
{
    await Task.Delay(2000);
    message = "Finished";      // The execution of this line will be delayed by 2 seconds.
}

private void button2_Click(object sender, EventArgs e)
{
    message = "Started";
    button1_Click(null, null); // Since this "method" is not awaited,
    MessageBox.Show(message);  // the execution of this line will NOT be delayed.
}

Теперь все, что я сделал, это переместил код, который был внутри метода асинхронныйMyMethodAsync(), в обработчик событий асинхронныйbutton1_Click, а затем вызвал этот обработчик событий с помощью button1_Click(null, null). Есть ли разница между этим и первым кодом? Нет, по сути, это одно и то же; в обоих случаях я вызвал асинхронный метод не дожидаясь этого.2

Если вы до сих пор со мной согласны, вы, вероятно, уже понимаете, почему ваш код работает не так, как ожидалось. Приведенный выше код (во втором случае) практически идентичен вашему. Разница в том, что я использовал button1_Click(null, null) вместо button1.PerfomClick(), который, по сути, делает то же самое.

Решение:

Если вы хотите дождаться завершения кода в button1_Click, вам нужно переместить все внутри button1_Click(если это асинхронный код) в метод async, а затем Ждите его в обаbutton1_Click и button2_Click. Это именно то, что вы сделали в своем Раздел "Правка", но имейте в виду, что button2_Click также должен иметь подпись async.


3If you thought the answer was something else, then you might want to check this article which explains the warning.

1The only difference is that in the first case, we could solve the problem by awaiting the method, however, in the second case, we can't do that because the "method" is not awaitable because the return type is voideven though it has an async signature.

2Actually, there are some differences between the two (e.g., the validation logic in PerformClick(), but these differences don't affect the end result in our current situation.

Я добавил async в отредактированный обработчик событий Button2, это была опечатка.

Poul Bak 05.09.2018 14:50

Другие вопросы по теме