问题 为什么不控制更新/刷新中间进程


我有一个带有statusLabel的Windows窗体(C#.NET),我似乎无法在事件处理程序方法的过程中进行更新。我的代码看起来像这样......

    void Process_Completed(object sender, EventArgs e)
    {

        string t = "Process is finished!";
        this.Invoke(new StatusLabelUpdator(updateStatusLabel), new object[] { t });
    }

    void Process_Started(object sender, EventArgs e)
    {
        string t = "Process has begun";
        this.Invoke(new StatusLabelUpdator(updateStatusLabel), new object[] { t });
    }

    private delegate void StatusLabelUpdator(string text);
    private void updateStatusLabel(string text)
    {
        StatusLabel1.Text = text;
        statusStrip1.Invalidate();
        statusStrip1.Refresh();
        statusStrip1.Update();
    }

当我运行代码时,一旦进程启动,就会触发Process_Started方法,几秒钟后会触发Process_Completed方法。出于某种原因,我不能让状态标签显示“过程已经开始”。它只显示“过程完成!”。正如您所看到的,我已尝试使包含状态标签但未成功的状态条无效,刷新和更新。我无法在statuslabel本身上调用update / refresh / invalidate,因为这些方法不可用。我究竟做错了什么?

添加信息:

通过单击窗体上的按钮启动“进程”,该窗体在单独的类中调用方法,如下所示:

public void DoSomeProcess()
{
    TriggerProcessStarted();
    System.Threading.Thread.Sleep(2000);   // For testing..
    TriggerProcessComplete();
}

在TriggerProcessxxxx方法中,我使用此代码触发事件...

var EventListeners = EH.GetInvocationList();    //EH is the appropriate EventHandler
if (EventListeners != null)
{
    for (int index = 0; index < EventListeners.Count(); index++)
    {
        var methodToInvoke = (EventHandler)EventListeners[index];
        methodToInvoke.BeginInvoke(this, EventArgs.Empty, EndAsyncEvent, new object[] { });
    }
}

最后,我补充说 Application.DoEvents() 到了 updateStatusLabel 方法,但它没有帮助。我仍然得到相同的结果。这是我的更新方法。

private void updateStatusLabel(string text)
{
    StatusLabel1.Text = text;
    statusStrip1.Refresh();
    Application.DoEvents(); 
}

所以我猜“处理”是在UI线程上进行的,但是在自己的线程上调用了eventhandler,然后在UI线程上调用控件更新。这是一种愚蠢的做事方式吗?注意:包含DoSomeProcess()方法的类位于我引用的单独的.NET ClassLibrary中。


8043
2018-02-26 13:04


起源



答案:


如果你正在做你的 处理 在UI线程上,当处理正在运行时,它将无法执行任何其他操作(如重绘更新的标签)。因此,例如,如果处理发生是因为用户单击按钮并由按钮单击处理程序触发(没有明确地将其放在另一个线程上),则它正在UI线程上运行。即使您更新了标签的文本,它也不会在收到绘制消息之前被绘制,此时它可能正在忙于处理。

答案是对a进行长时间处理 单独的线程。黑客(恕我直言)即将使用 Application.DoEvents 让UI线程在处理期间执行一些UI操作。如果您在更新标签之后和开始处理之前放入其中一个,那么标签将重新粉刷的可能性非常高。但是,在处理过程中,不会再处理任何油漆事件(当有人在应用程序上移动另一个应用程序窗口并返回时,会导致半绘制窗口等)。因此,我称之为黑客(即使,呃,嗯,我已经知道这样做了:-))。

编辑 根据您的修改进行更新:

回覆

所以我猜“处理”是在UI线程上发生的,但是事件处理程序是在它自己的线程上调用的......

我在假设 DoSomeProcess 从UI线程触发(例如,直接响应按钮点击或类似)。如果是,那么是的,您的处理肯定是在UI线程上。因为 TriggerProcessStarted 触发你的回调 异步 通过 BeginInvoke,你不知道它什么时候会运行,但无论如何你的代码会立即启动进入处理,永不屈服,所以没有其他人能够抓住那个线程。由于这是UI线程,对委托的调用将阻止 Invoke 调用设置标签的文本,因此必须等待UI线程(忙于处理)。 (并且假设它安排在不同的线程上;我无法100%说服自己,因为微软有两种不同的方式 BeginInvokes - 其中一位设计师承认的IIRC是一个真正愚蠢的想法 - 自从我与这些东西战斗以来已经有一段时间了。)

如果你做了 TriggerProcessStarted 同步调用你的回调,你应该没问题。但理想情况下,在自己的线程上安排处理(如果它不是UI)。


16
2018-02-26 13:07



我试过你的“黑客”,但它似乎没有帮助。我现在在上面的问题中添加了更多细节。 - PICyourBrain
编辑后:你是对的。我单独离开了BeginInvoke但更改了我的按钮单击事件以在其自己的线程上启动DoSomeWork。修好了! - PICyourBrain
很酷,很高兴帮忙! - T.J. Crowder


答案:


如果你正在做你的 处理 在UI线程上,当处理正在运行时,它将无法执行任何其他操作(如重绘更新的标签)。因此,例如,如果处理发生是因为用户单击按钮并由按钮单击处理程序触发(没有明确地将其放在另一个线程上),则它正在UI线程上运行。即使您更新了标签的文本,它也不会在收到绘制消息之前被绘制,此时它可能正在忙于处理。

答案是对a进行长时间处理 单独的线程。黑客(恕我直言)即将使用 Application.DoEvents 让UI线程在处理期间执行一些UI操作。如果您在更新标签之后和开始处理之前放入其中一个,那么标签将重新粉刷的可能性非常高。但是,在处理过程中,不会再处理任何油漆事件(当有人在应用程序上移动另一个应用程序窗口并返回时,会导致半绘制窗口等)。因此,我称之为黑客(即使,呃,嗯,我已经知道这样做了:-))。

编辑 根据您的修改进行更新:

回覆

所以我猜“处理”是在UI线程上发生的,但是事件处理程序是在它自己的线程上调用的......

我在假设 DoSomeProcess 从UI线程触发(例如,直接响应按钮点击或类似)。如果是,那么是的,您的处理肯定是在UI线程上。因为 TriggerProcessStarted 触发你的回调 异步 通过 BeginInvoke,你不知道它什么时候会运行,但无论如何你的代码会立即启动进入处理,永不屈服,所以没有其他人能够抓住那个线程。由于这是UI线程,对委托的调用将阻止 Invoke 调用设置标签的文本,因此必须等待UI线程(忙于处理)。 (并且假设它安排在不同的线程上;我无法100%说服自己,因为微软有两种不同的方式 BeginInvokes - 其中一位设计师承认的IIRC是一个真正愚蠢的想法 - 自从我与这些东西战斗以来已经有一段时间了。)

如果你做了 TriggerProcessStarted 同步调用你的回调,你应该没问题。但理想情况下,在自己的线程上安排处理(如果它不是UI)。


16
2018-02-26 13:07



我试过你的“黑客”,但它似乎没有帮助。我现在在上面的问题中添加了更多细节。 - PICyourBrain
编辑后:你是对的。我单独离开了BeginInvoke但更改了我的按钮单击事件以在其自己的线程上启动DoSomeWork。修好了! - PICyourBrain
很酷,很高兴帮忙! - T.J. Crowder