C# 为什么不控制更新/刷新中间过程

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2341731/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-07 01:36:41  来源:igfitidea点击:

Why won't control update/refresh mid-process

c#.netcontrolsrefresh

提问by PICyourBrain

I have a windows form (C#.NET) with a statusLabel that I can not seem to get to update in the middle of a process in event handler methods. My code looks like this...

我有一个带有 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();
    }

When I run the code, once the process starts, the Process_Started method is triggered, and a couple seconds later the Process_Completed method is triggered. For some reason I can not get the status label to ever display "Process has begun". It only ever displays "Process is finished!". As you can see I have tried invalidating, refreshing and updating the status strip which contains the status label but no success. I can't call update/refresh/invalidate on the statuslabel itself because those methods are not available to it. What am I doing wrong?

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

ADDED INFO:

添加信息:

The "process" is started by a button click on the form which calls a method in a separate class that looks like this:

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

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

and inside the TriggerProcessxxxx methods I trigger the events using this code...

在 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[] { });
    }
}

Finally, I have added Application.DoEvents()to the updateStatusLabelmethod but it did not help. I am still getting the same result. Here is my update method.

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

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

So I guess the "processing" is taking place on the UI thread but eventhandler is invoked on it's own thread which then invokes the control update back on the UI thread. Is this a dumb way of doing things? Note: The class that contains the DoSomeProcess() method is in a separate .NET ClassLibrary that i am referencing.

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

采纳答案by T.J. Crowder

If you're doing your processingon the UI thread, it won't be able to do anything else (like redraw updated labels) while the processing is running. So for instance, if the processing is happening because the user clicked a button and is triggered by the button click handler (without explicitly placing it on another thread), it's running on the UI thread. Even though you update the label's text, it doesn't get drawn until it receives a paint message, at which point it's probably busy doing your processing.

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

The answer is to do long-running processing on a separate thread. The hack (IMHO) is to use Application.DoEventsto let the UI thread do some UI stuff during your processing. If you put one of those after updating the label and before you start your processing, odds are pretty high the label will get repainted. But then, during the processing, no further paint events can get processed (leading to half-drawn windows when someone moves another app window over your app and back, etc.). Hence my calling it a hack (even though, er, um, I've been known to do it :-) ).

答案是在单独的线程上进行长时间运行的处理。hack (恕我直言) 是用来Application.DoEvents让 UI 线程在你的处理过程中做一些 UI 的事情。如果您在更新标签之后和开始处理之前放置其中之一,则标签被重新绘制的可能性非常高。但是,在处理过程中,无法处理进一步的绘制事件(当有人将另一个应用程序窗口移到您的应用程序上方并返回时,导致窗口绘制一半,等等)。因此我称它为黑客(尽管,呃,嗯,我已经知道这样做了:-))。

EditUpdate based on your edits:

根据您的编辑编辑更新:

Re

关于

So I guess the "processing" is taking place on the UI thread but eventhandler is invoked on it's own thread...

所以我猜“处理”是在 UI 线程上进行的,但是 eventhandler 是在它自己的线程上调用的......

I'm assuming DoSomeProcessis triggered from the UI thread (e.g., in direct response to a button click or similar). If so, then yes, your processing is definitely on the UI thread. Because TriggerProcessStartedtriggers your callback asynchronouslyvia BeginInvoke, you have no idea when it will run, but in any case your code then immediately launches into processing, never yielding, so no one else is going to be able to grab that thread. Since that's the UI thread, the call to the delegate will block on the Invokecall setting the label's text, whereupon it has to wait for the UI thread (which is busy processing). (And that's assuming it's scheduled on a different thread; I couldn't 100% convince myself either way, because Microsoft has two different BeginInvokes -- which IIRC one of the designers has acknowledged was a Really Dumb Idea -- and it's been a while since I fought with this stuff.)

我假设DoSomeProcess是从 UI 线程触发的(例如,直接响应按钮单击或类似操作)。如果是这样,那么是的,您的处理肯定在 UI 线程上。因为通过异步TriggerProcessStarted触发你的回调,你不知道它什么时候运行,但无论如何你的代码会立即开始处理,永远不会让步,所以没有人能够抓住那个线程。由于那是 UI 线程,对委托的调用将在设置标签文本的调用上阻塞,因此它必须等待 UI 线程(忙于处理)。(假设它被安排在不同的线程上;无论如何我都不能 100% 说服自己,因为 Microsoft 有两个不同的BeginInvokeInvokeBeginInvokes - IIRC 的一位设计师承认这是一个非常愚蠢的想法 - 我已经有一段时间没有与这些东西抗争了。)

If you make the TriggerProcessStartedcalls to your callbacks synchronous, you should be okay. But ideally, schedule the processing (if it's not doing UI) on its own thread instead.

如果您TriggerProcessStarted对回调进行同步调用,则应该没问题。但理想情况下,应在其自己的线程上安排处理(如果它不执行 UI)。