超时调用

时间:2009-07-20 08:21:36

标签: .net timeout invoke begininvoke

我们有一些代码在后台线程中运行,需要弹出一个对话框或其他一些用户交互,所以我们通常会对UI线程进行Invoke调用:

Control.Invoke(SomeFunction);

void SomeFunction()
{
  ...
}

但是,我们遇到了一个错误,我们的UI线程有时没有立即响应Invoke调用 - 我们追踪到这样一个事实,即UI线程当前正在执行一个没有'的跨进程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”都是

即使前两件事情可以解决,我们仍然有一般的ickyness。有没有更好的方法来解决这个问题?

2 个答案:

答案 0 :(得分:0)

将跨进程DCOM调用移动到另一个线程。您显然挂起了UI线程,这是完全不可接受的。解决这个问题,你的幻影问题(OP)也消失了。

答案 1 :(得分:0)

这是在GUI线程上运行某些东西时常见的线程问题,这种症状会影响所有开发人员。

如果要创建一个单独的线程,在其中显示实际的进度对话框,另一个线程用于执行DCOM调用,则只需要在两个线程之间移动ManuaResetEvent同步。这样做的好处是不会锁定GUI线程,因为创建进度表单的单独线程将创建自己的消息队列,而用于运行DCOM调用的第二个线程不必锁定任何GUI线程。

它需要一些仔细的同步,但一旦完成,看起来很漂亮:

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线程,使用它时应该没有更多或道德的“代码味道”问题。我们应该使用任何工具或技术来帮助我们完成工作。