我们有一些代码在后台线程中运行,需要弹出一个对话框或其他一些用户交互,所以我们通常会对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)
即使前两件事情可以解决,我们仍然有一般的ickyness。有没有更好的方法来解决这个问题?
答案 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线程,使用它时应该没有更多或道德的“代码味道”问题。我们应该使用任何工具或技术来帮助我们完成工作。