超时调用

发布于 2024-07-27 12:05:21 字数 1157 浏览 5 评论 0原文

我们有一些代码在后台线程中运行,需要弹出对话框或其他一些用户交互,因此我们对 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 调用实际上没有完全挂起,它最终会运行
  • 其次,存在明显的可怕的竞争条件
  • 最后,还有整个“整个事情的糟糕性

即使前两件事可以解决,我们仍然有普遍的恶心。 有更好的方法来解决这个问题吗?

We have some code running in a background thread which needs to pop a dialog or some other user interaction, so we do the usual Invoke call on to the UI thread:

Control.Invoke(SomeFunction);

void SomeFunction()
{
  ...
}

But, we came across a bug, our UI thread is sometimes not immediately responding to the Invoke call - we tracked it down to the fact that the UI thread was currently performing a cross process DCOM call that hadn't returned yet. Once the DCOM call had returned our function would be called, but until then it appeared that the Invoke call had hung.

My solution for this was to introduce a timeout:

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();

  ...
}

This worked in the "works on my machine sense", but it had a number of flaws.


(source: codinghorror.com)

  • Firstly the function is still invoked, even if the timeout occurs - if the DCOM call hadn't actually completely hung, it will eventually run
  • Secondly, there is the obvious horrible race condition
  • Finally, there is the whole "Arrgh"-ness of the whole thing

Even if the first two things could be resolved, we still have the general ickyness. Is there a better way to resolve this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

北方的韩爷 2024-08-03 12:05:21

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

Move the cross-process DCOM call to another thread. You are obviously hanging the UI thread, which is completely unacceptable. Fix that and your phantom problem (the OP) goes away too.

梦初启 2024-08-03 12:05:21

当涉及到在 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 线程中创建的,因此使用它不应该有更多或道德的“代码味道”问题。 我们应该使用任何有助于我们完成工作的工具或技术。

This is a common threading problem when it comes to running something on the GUI thread and this symptom affects all breeds of developers.

If you were to create a separate thread where you display the actual progress dialog and another thread to perform the DCOM call, it would simply entail moving the ManuaResetEvent synchronization between the two threads. This has the benefit of not locking the GUI thread since the separate thread that creates the progress form will have its own message queue created, and the second thread used to run the DCOM call does not have to lock any GUI thread.

It does require some careful sycnhronizing but once done, it's beautiful to see in action:

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();
}

Notes
Some might argue against using the Application.DoEvents() method, but consider that DoEvents forces any pending Windows messages on the current calling thread's message queue to be processed and since the call is made in a different thread (the one that created the progress dialog) and not the GUI thread, there should be no more or ethical "code smell" issues with using it. We should use whatever tools or technique help us get the job done.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文