Invoke and Delegates in events - 为什么动作会以这种(意外的)顺序发生?

Invoke and Delegates in events - Why does the actions happen in this (unexpected) order?

我有一个 可以通过使用 delegate.

来解决

我以前没有和委托一起工作过,所以我测试了一下,发现了这个奇怪的行为:

When using delegates the actions happen in a for me completely unexpected order.

第一个样本:

string Tracker = "";

private void button1_Click(object sender, EventArgs e)
{
    Tracker = "A";

    BeginInvoke((MethodInvoker)delegate
    {
        Tracker += "B";
    });

    Tracker += "C";
} 

此后Tracker包含ACB。不知道它是如何工作的,但它似乎按照描述工作。


第二个样本:

string Tracker = "";

private void button2_Click(object sender, EventArgs e)
{
    Tracker = "A";
    MessageBox.Show(Tracker);

    BeginInvoke((MethodInvoker)delegate
    {
        Tracker += "B";
        MessageBox.Show(Tracker);
    });

    Tracker += "C";
    MessageBox.Show(Tracker);
}

此后Tracker还包含ACB,但消息框显示顺序为:

... 而不是我期望的 A AC ACB

解释是什么?

示例 1:

private void button1_Click(object sender, EventArgs e)  
{
    Tracker = "A";

    BeginInvoke((MethodInvoker)delegate
    {
        Tracker += "B";
    });

   Tracker += "C";
} 

已分配第一个跟踪器, 接下来,您将一个委托异步推送到 GUI 队列,因此直到它有机会才会发生。 所以下一个 Tracker 就变成了 AC。 然后 GUI 线程就释放了,因此你得到了 ACB。 示例 2:

string Tracker = "";

private void button2_Click(object sender, EventArgs e)
{
    Tracker = "A";
    MessageBox.Show(Tracker);

    BeginInvoke((MethodInvoker)delegate
    {
        Tracker += "B";
        MessageBox.Show(Tracker);
    });

    Tracker += "C";
    MessageBox.Show(Tracker);
}

仍然发生同样的事情: 首先会出现 A 的消息框,我认为这是合乎逻辑的。 接下来委托被推送到 GUI 线程的队列中 然后 Tracker 变为 AC,消息框被调度到 GUI 线程上的 运行 并被调度为显示 AC。但是,因为在 GUI 线程上安排了其他事情,所以其他事情将首先发生。 所以先delegate要执行,然后AC要show。

如果我的解释在某些时候仍然不清楚,post 评论,并告诉我哪一部分需要更多阐述。 但最重要的是,操作被推送到 GUI 线程,并由 GUI 线程按照它们出现在 GUI 线程中的顺序执行。

这需要了解 UI 线程的调度程序循环是如何工作的。当您使用 BeginInvoke() 时,您 post 向该循环发送一条消息,告诉它去查看调用队列。该消息仅在 您的代码停止 运行ning 和 Click 事件处理程序 returns 后才被处理。因此接下来是 Tracker += "B"; 语句将在您的 Click 事件处理程序停止后执行 运行ning.

MessageBox class 也使用调度程序循环,但它是它自己的,与您程序的调度程序循环无关。这允许它是 modal。因此,您的 Click 方法在最后一次 MessageBox.Show() 调用时停止,其调度程序循环开始调度消息并触发被调用的方法。另一个 MessageBox.Show() 调用,您先看到它。第二次调用的调度程序卡住,直到对话框关闭。

这是一种重入,会导致很难诊断错误。它使 Application.DoEvents() 方法变得如此危险。通过代码 运行s 的顺序推理变得非常困难。 MessageBox.Show() 实际上等同于 DoEvents()。但不是那么讨厌,它就像一个对话框并禁用所有其他 windows 最大限度地减少了事故的数量。它消除了用户以意外顺序触发事件 运行 的可能性。但是并没有完全消除它们,它不能停止代码。


FWIW,像这样使用 BeginInvoke() 可能非常有用。它可以解决许多由生成事件但在事件处理程序中执行 "too much" 时响应不佳的 balky classes 引起的问题。 TreeView class 以非常喜怒无常 class 着称。通过使用 BeginInvoke(),您可以确定代码 运行s after balky class 中的代码已完成。