Почему в вызове компонента Cross Thread UI нет EndInvoke?

Я пытался добавить строку в какой-то ListBox, который у меня есть в приложении (простая winform) - и я сделал это с помощью BeginInboke

 myListBox.BeginInvoke(new Action(delegate()
 {
       myListBox.Items.Add( "some string" )); 
 }));

После того, как я снова прочитал эти 3 строки - и я не понимаю, почему в каком-либо примере интерфейса с несколькими нитями, который я смотрю в Google и MSDN, я не вижу никакого вызова EndInvoke? Есть ли причина не вызывать EndInvoke в этом случае?

2 ответа

Решение

Это был неудачный выбор имен в.NET. Методы Control.BeginInvoke и Dispatcher.BeginInvoke имеют то же имя, что и методы делегата, но работают совершенно по- разному. Основные отличия:

  • Метод BeginInvoke() делегата всегда безопасен от типа и имеет те же аргументы, что и объявление делегата. Это полностью отсутствует в версиях Control/Dispatcher, аргументы передаются через массив params типа object[]. Компилятор не скажет вам, если вы ошиблись аргументом, он бомбит во время выполнения

  • Метод Invoke () делегата запускает цель делегата в том же потоке. Не относится к Control/Dispatcher.Invoke(), они перенаправляют вызов в поток пользовательского интерфейса

  • Исключение, которое выдается в цели BeginInvoke() делегата, перехватывается и не приводит к сбою программы. Быть переброшенным при вызове EndInvoke(). Это совсем не так для Control/Dispatcher.BeginInvoke(), они вызывают исключение в потоке пользовательского интерфейса. Без приличного способа отловить исключение - одна из причин, по которой существует Application.UnhandledException.

  • Вызов метода EndInvoke () делегата является обязательным, в противном случае он вызывает утечку ресурсов в течение 10 минут. Это не требуется для методов Control/Dispatcher.BeginInvoke(), и вы никогда не делаете это на практике.

  • Использование Control/Dispatcher.Invoke () сопряжено с риском, что может привести к взаимоблокировке. Срабатывает, когда поток пользовательского интерфейса не готов вызвать цель и делает что-то неразумное, например, ожидание завершения потока. Не проблема для делегата, не в последнюю очередь потому, что его метод Invoke () не использует поток.

  • Вызов Control/Dispatcher.BeginInvoke() в потоке пользовательского интерфейса является поддерживаемым сценарием. Как и ожидалось, цель все еще работает в потоке пользовательского интерфейса. Но позже, после того, как поток пользовательского интерфейса снова простаивает и снова входит в цикл диспетчера. На самом деле это очень полезная функция, она помогает решить сложные проблемы с повторным входом. Особенно в обработчиках событий для элементов управления пользовательского интерфейса, которые будут плохо себя вести, когда вы запускаете код со слишком большим количеством побочных эффектов.

Большой список с подробностями реализации. Версия TLDR, безусловно, такова: "Они не имеют ничего общего, не вызывая EndInvoke - это нормально и совершенно нормально".

Control.BeginInvoke не похоже полностью следовать обычному BeginX / EndX шаблон ака модель асинхронного программирования (APM). Обычно вы должны позвонить EndX для каждого BeginX, но в случае Control.BeginInvoke это не обязательно:

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

- из раздела "Примечания" на справочной странице MSDN для Control.BeginInvoke (акцент мной)

И на практике это вряд ли когда-либо необходимо. Это связано с тем, что метод обычно вызывается для выполнения некоторого кода в потоке пользовательского интерфейса, который обновляет пользовательский интерфейс. Обновление пользовательского интерфейса обычно не дает никакого возвращаемого значения, поэтому вы не захотите вызывать EndInvoke,

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