Вызвать с таймаутом - PullRequest
1 голос
/ 20 июля 2009

У нас есть некоторый код, работающий в фоновом потоке, который должен вызвать диалог или другое взаимодействие с пользователем, поэтому мы делаем обычный вызов Invoke в потоке пользовательского интерфейса:

Control.Invoke(SomeFunction);

void SomeFunction()
{
  ...
}

Но мы столкнулись с ошибкой, наш поток пользовательского интерфейса иногда не сразу отвечает на вызов Invoke - мы отследили его до того факта, что поток пользовательского интерфейса в настоящее время выполнял перекрестный вызов DCOM, который не вернулся еще. Как только вызов DCOM вернется, будет вызвана наша функция, но до этого казалось, что вызов Invoke завис.

Мое решение для этого было ввести тайм-аут:

ManualResetEvent invokeEvent = new ManualResetEvent();
var result = Control.BeginInvoke(SomeFunction, invokeEvent);

if (!invokeEvent.WaitOne(1000))
  throw new Exception("Not responding");

Control.EndInvoke(result);

void SomeFunction(ManualResetEvent invokeEvent)
{
  invokeEvent.Set();

  ...
}

Это работало в «смысле моей машины», но имело ряд недостатков.


(источник: codinghorror.com )

  • Во-первых, функция все еще вызывается, даже если время ожидания истекло - если вызов DCOM фактически не завис полностью, он в конечном итоге запустится
  • Во-вторых, очевидное ужасное состояние гонки
  • Наконец, есть вся "Arrgh" -нессность всего этого

Даже если первые две вещи могут быть решены, у нас все еще есть общая слабость. Есть ли лучший способ решить эту проблему?

Ответы [ 2 ]

0 голосов
/ 20 июля 2009

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

Если бы вы создали отдельный поток, в котором отображалось бы диалоговое окно фактического прогресса, и другой поток для выполнения вызова DCOM, это просто повлекло бы за собой перемещение синхронизации ManuaResetEvent между двумя потоками. Преимущество этого заключается в том, что вы не блокируете поток графического интерфейса, поскольку отдельный поток, который создает форму выполнения, будет создавать свою собственную очередь сообщений, а второй поток, используемый для выполнения вызова DCOM, не должен блокировать какой-либо поток графического интерфейса.

Это требует некоторого тщательного изучения, но когда это сделано, это прекрасно видеть в действии:

private ManualResetEvent _event = new ManualResetEvent(false);
...

private void StartTheComProgressCall()
{
    _event.Reset();

    ThreadPool.QueueUserWorkItem(StartProgressDialog);
    ThreadPool.QueueUserWorkItem(StartDCOMCall);

    // there's various possibilities to perform here, we could ideally 1) wait on the
    // event to complete, 2) run a callback delegate once everything is done
    // 3) fire an event once completed
}

private void StartProgressDialog(object state)
{
    ProgressDialog dialog = new ProgressDialog();
    dialog.Show();

    while(!_event.WaitOne(0))
        Application.DoEvents();

    dialog.Close();
}

private void StartDCOMCall()
{
    ...
   <perform your DCOM routines here>

    // once the call is done, remember to trigger that it's complete
    // so that blocking threads can continue to do what they need to do
    _event.Set();
}

Примечания Некоторые могут возразить против использования метода Application.DoEvents(), но считают, что DoEvents заставляет обрабатывать любые ожидающие сообщения Windows в очереди сообщений текущего вызывающего потока, и поскольку вызов выполняется в другом потоке (который создал диалог прогресса) ), а не поток GUI, не должно быть больше или этических проблем с «запахом кода» при его использовании. Мы должны использовать любые инструменты или технику, которые помогут нам выполнить работу.

0 голосов
/ 20 июля 2009

Переместить межпроцессный вызов DCOM в другой поток. Вы очевидно зависаете в потоке пользовательского интерфейса, что совершенно недопустимо. Исправьте это, и ваша проблема с фантомом (ОП) исчезнет.

...