Причины, по которым Control.BeginInvoke не будет выполнять делегат? - PullRequest
2 голосов
/ 24 января 2012

Обзор

Существуют ли объяснения для Control.BeginInvoke (), чтобы не выполнять делегат, которому он передан?

Пример кода

Мы приняли следующий шаблон внаши приложения Winforms для безопасного выполнения определенной работы пользовательского интерфейса в потоке пользовательского интерфейса:

private Control hiddenControl = new Control();

private void uiMethod()
{
  MethodInvoker uiDelegate = new MethodInvoker(delegate()
  {
    Logging.writeLine("Start of uiDelegate");
    //ui releated operations
    childDialog = new ChildDialog();
    childDialow.show();
    Logging.writeLine("End of uiDelegate");
  });

  if (hiddenControl.InvokeRequired)
  {
    Logging.writeLine("Start of InvokeRequired block");
    hiddenControl.BeginInvoke(uiDelegate);
    Logging.writeLine("End of InvokeRequired block");
  }
  else
  {
    uiDelegate();
  }
}

Здесь мы явно создаем элемент управления «hiddenControl» для запуска делегатов в потоке пользовательского интерфейса.Мы никогда не вызываем endInvoke, потому что, по-видимому, не требуется для Control.BeginInvoke, и нам никогда не нужно возвращать значение, так как наши методы в любом случае просто манипулируют пользовательским интерфейсом.

Хотя этот шаблон и очень многословный,быть относительно скважиной принятым решением .Однако есть свидетельство , что даже этот шаблон может работать не во всех ситуациях.

Наблюдения

Я не исключаю ошибку приложения и обвиняю WinForms,В конце концов, select, вероятно, не нарушен .Однако я затрудняюсь объяснить, почему делегат, похоже, вообще не баллотируется.

В нашем случае мы иногда наблюдаем, что сообщение журнала «Start of uiDelegate» никогда не выполняется в определенных сценариях потоков, даже если «Начало блока InvokeReqiured» и «Конец блока InvokeRequired» выполняются успешно.

Было очень трудно воспроизвести это поведение, потому что наше приложение поставляется в виде DLL;наши клиенты запускают его в своих собственных приложениях.Поэтому мы не можем дать никаких гарантий того, как или в каком потоке эти методы могут быть вызваны.

Мы исключили голодание потока пользовательского интерфейса, поскольку наблюдается, что пользовательский интерфейс не блокируется.Предположительно, если пользовательский интерфейс обновляется, тогда насос сообщений работает и доступен для извлечения сообщений из очереди сообщений и выполнения их делегатов.

Сводка

Учитывая эту информацию, есть ли что-нибудь, чтомы можем попытаться сделать эти звонки более пуленепробиваемыми?Как упоминалось ранее, мы относительно мало контролируем другие потоки в данном приложении и не контролируем, в каком контексте эти методы вызываются.

Что еще может повлиять на успешное выполнение делегатов в Control.BeginInvoke () илинет?

Ответы [ 3 ]

7 голосов
/ 24 января 2012

Согласно MSDN InvokeRequired может вернуть false даже в тех случаях, когда InvokeRequired должно быть true, а именно в том случае, если вы получаете доступ к InvokeRequired до Handle этогоЭлемент управления / форма (или его родитель) создан.

По сути, ваша проверка не завершена, что приводит к полученному результату.

Вам необходимо проверить IsHandleCreated - если это false, то у вас проблемы, потому что Invoke / BeginInvoke понадобится, НО не будет работать надежно, так как Invoke / BeginInvoke проверяет, какой поток создал Handle, чтобы выполнить ихмагия ...

Только если IsHandleCreated равно true, вы действуете на основе того, что возвращает InvokeRequired - что-то вроде:

if (control.IsHandleCreated)
{
    if (control.InvokeRequired)
    {
        control.BeginInvoke(action);
    }
    else
    {
        action.Invoke();
    }
}
else 
{ 
    // in this case InvokeRequired might lie - you need to make sure that this never happens! 
    throw new Exception ( "Somehow Handle has not yet been created on the UI thread!" );
}

Таким образом, следующееважно избежать этой проблемы

Всегда проверяйте, что Handle уже создан ДО первого доступа к потоку, отличному от потока пользовательского интерфейса.

Согласно MSDN вам просто нужно сослаться на control.Handle в потоке пользовательского интерфейса, чтобы принудительно создать его - в вашем коде этодолжно произойти ДО того, как вы в первый раз получите доступ к этому элементу управления / форме из любого потока, который не является потоком пользовательского интерфейса.

Для других возможностей см. ответ от @JaredPar.

3 голосов
/ 24 января 2012

Вы, вероятно, выбрасываете исключение при первом вызове регистрации. Однако я бы посоветовал взглянуть на TPL (если вы используете .Net 4.0). Задачи могут сделать ваш код более читабельным, и вы можете сделать что-то вроде следующего:

Task t = Task.Factory.StartNew(()=>{...Do Some stuff not on the UI thread...});
Task continuationTask = t.ContinueWith((previousTask)=>{...Do your UI stuff now 
         (let the Task Scheduler deal with jumping back on the UI thread...},
    TaskScheduler.FromCurrentSynchronizationContext());

Затем вы также можете легко проверить наличие исключений в любой из задач с помощью continueationTask.Exceptions.

3 голосов
/ 24 января 2012

Есть несколько причин, по которым вызов BeginInvoke может не удаться.

  1. Элемент управления и все его родители не создали внутреннюю ручку. Это вызвало бы исключение на сайте вызова
  2. Элемент управления опубликовал делегат, но был уничтожен до его фактического запуска в потоке пользовательского интерфейса
  3. Поток пользовательского интерфейса прекращает прокачивать сообщения (обычно поток заканчивается)

Звучит так, что вполне возможно, что # 2 вызывает ваше горе здесь. Я пару раз сталкивался с этой проблемой при разработке приложений winform. Это вызвало у меня столько горя, что я перешел с Control.BeginInvoke на SynchronizationContext.Current.Post. Экземпляр SynchronizationContext.Current будет жить в течение жизни UI-потока в приложении WinForms, и IMHO немного более надежен, чем вызов определенного Control

...