Button.PerformClick () не ожидает асинхронного обработчика событий - PullRequest
0 голосов
/ 04 сентября 2018

У меня проблема в приложении 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(), он просто запускает задание, а затем продолжает.

Edit:

В этом простом коде я могу заставить его работать, вызывая 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 (или обработчика событий) должен дать компилятору Предупреждение : " Вы вызываете асинхронный метод void, он не будет ожидаться! "

1 Ответ

0 голосов
/ 04 сентября 2018

Похоже, у вас есть некоторые неправильные представления о том, как 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.
}

Примечание: компилятор выдаст нам предупреждение, но давайте проигнорируем это для тестирования.

Теперь, что вы ожидаете от 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.
}

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

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

Решение:

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


1 Если вы подумали, что ответ был чем-то другим, то вы можете проверить эту статью , в которой объясняется предупреждение.

2 Единственное отличие состоит в том, что в первом случае мы могли бы решить проблему, ожидая метода, однако во втором случае мы не можем этого сделать, потому что метод " "не является ожидаемым , потому что тип возвращаемого значения void , хотя он имеет async подпись .

3 На самом деле между ними есть некоторые различия (например, логика проверки в PerformClick(), но эти различия не влияют на конечный результат в нашей текущей ситуации.

...