2009-07-20 167 views
1

我们有一些代码在后台线程它需要弹出一个对话框或其他一些用户交互运行,所以我们做平常Invoke呼叫到UI线程:调用与超时

Control.Invoke(SomeFunction); 

void SomeFunction() 
{ 
    ... 
} 

但是,我们遇到了一个bug,我们的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(); 

    ... 
} 

这曾在“关于我的机器感的作品”,但它有一些缺陷。

http://www.codinghorror.com/blog/images/works-on-my-machine-stamped.png

  • 首先功能仍然调用,即使发生超时 - 如果DCOM调用实际上并没有完全挂起,它最终会
  • 其次,有明显的可怕的种族条件
  • 最后是整个事情整个“Arrgh” -ness

即使第一次两件事情可以得到解决,我们仍然有根口腔疾病。有没有更好的方法来解决这个问题?

回答

0

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

+0

非常抱歉,我们不拥有使跨进程DCOM调用的代码。即使我们这样做了,我们仍然需要在我们自己的代码中将其作为一个普遍问题来解决,因为可能会加载任何其他代码,这可能会导致这个普遍问题。好主意,但 – 2009-07-20 08:31:30

0

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

如果您要创建一个单独的线程来显示实际的进度对话框和另一个线程来执行DCOM调用,它只需要移动两个线程之间的ManuaResetEvent同步。这具有不锁定GUI线程的好处,因为创建进度表单的单独线程将创建自己的消息队列,并且用于运行DCOM调用的第二个线程不必锁定任何GUI线程。

它确实需要一些小心sycnhronizing但一旦完成,它的美丽在行动上看到:

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