У меня проблема в приложении 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?
Вывод:
Хорошо, теперь я понял, вывод такой:
Никогда не вызывайте PerformClick, если обработчик событий - async. Это не будет await!
Никогда не вызывайте обработчик событий async напрямую. Это не будет await!
Осталось отсутствие документации по этому поводу:
Button.PerformClick должен иметь Предупреждение на странице документа:Button.PerformClick «Вызов PerformClick не будет ожидать асинхронных обработчиков событий.»
async void (или обработчика событий) должен дать компилятору Предупреждение: "Вы вызываете метод async void, он не будет ожидаться!"У вас Конечно все еще говорит "Запущено" через 2 секунды? если вы продолжаете нажимать на свой button2, это означает, что сообщение меняется на «Запущено» в течение 2 секунд, прежде чем оно снова изменится на «Готово».
В том-то и дело, что это не ожидание.
Нет, это в ожидании, это просто не блокировка поток / пользовательский интерфейс, поэтому вы можете продолжать нажимать кнопки и заставлять его ждать столько раз, сколько хотите.
Он работает как задумано. При вызове await MyMethodAsync(); система проверяет, вернулся ли MyMethodAsync. Это не так из-за Task.Delay(2000);, поэтому он возвращается к button2_Click и печатает "Начал". Разумеется, message = "Started"; уже выполнен.
Вы ошибаетесь, смысл «ожидания» метода в том, что он не должен двигаться дальше, пока ожидаемый метод не вернется.
Вы можете отправить его в Microsoft как ошибку, чтобы исправить это.
@PoulBak И button1_Click не уходит, пока не вернется MyMethodAsync. Но button2_Click не ожидает (и не может) ждать button1_Click, поэтому продолжает двигаться дальше.
Ничего необычного здесь не происходит. Правильная ментальная модель состоит в том, что код после, выражение ожидания, выполняется позже, как если бы он был активирован таймером. Нет никакого механизма для блокировки метода в потоке пользовательского интерфейса, кроме ShowDialog () и ужасного Application.DoEvents (). Async / await, которого страшно из-за высокой вероятности возникновения неприятных ошибок повторного входа, не решает эту фундаментальную проблему.
@PoulBak я ожидал, что код покажет Started, а через две секунды он покажет Finished. Однако опубликованный вами код не позволяет вам увидеть этот переход. Он показывает содержимое поле, когда вы нажимаете кнопку. Если щелкнуть достаточно быстро, вы увидите Started. Если вы хотите, например, в течение 5 секунд, вы увидите Finished. Вместо того, чтобы сохранять значение в поле, попробуйте изменить заголовок формы. Здесь нет ошибки или чего-то неожиданного





Похоже, у вас есть некоторые неправильные представления о том, как работают 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, это была опечатка.
вызовите экзорциста!