Winforms更新具有高性能 [英] Winforms updates with high performance

查看:63
本文介绍了Winforms更新具有高性能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我用一些背景信息设置这个问题,我们有一个长期运行的过程,它将以Windows窗体生成数据.因此,显然需要某种形式的多线程来保持形式的响应性.但是,我们还要求表单每秒更新多次,同时仍保持响应状态.

这是一个使用后台工作线程的简单测试示例:

void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        int reportValue = (int)e.UserState;
        label1.Text = reportValue;
        //We can put this.Refresh() here to force repaint which gives us high repaints but we lose
        //all other responsiveness with the control

    }

    void bw_DoWork(object sender, DoWorkEventArgs e)
    {
            for (int x = 0; x < 100000; x++)
            {     
              //We could put Thread.Sleep here but we won't get highest performance updates
                bw.ReportProgress(0, x);                    
            }
    }

请参阅代码中的注释.另外,请不要质疑我为什么要这个.问题很简单,我们如何在保持响应能力的同时更新表单时达到最高的保真度(大多数重涂)?强制重绘确实可以为我们提供更新,但是我们不处理Windows消息.

我也尝试放置DoEvents,但这会导致堆栈溢出.我需要某种方式说如果您最近没有处理过Windows消息".我还可以看到,可能需要略有不同的模式才能实现这一目标.

似乎我们需要处理一些问题:

  1. 通过非UI线程更新表单.有很多解决此问题的方法,例如调用,同步上下文,后台工作器模式.
  2. 第二个问题是太多的更新使Form泛滥成灾,这阻碍了消息处理,而这正是我的问题真正关注的问题.在大多数示例中,通过任意等待来减慢请求速度或仅更新每个X%的速度,这是微不足道的处理.这些解决方案都不适合实际应用,也不符合响应标准时的最大更新.

关于如何处理此问题的一些初步想法:

  1. 在后台工作器中对项目进行排队,然后在UI线程中调度它们.这样可以确保每个项目都被绘制,但是会导致我们不希望的延迟.
  2. 也许使用TPL
  3. 也许在UI线程中使用计时器来指定刷新值.这样,我们可以以可以处理的最快速度获取数据.它将需要跨线程访问/共享数据.


更新,我已经更新为使用计时器来读取后台工作线程更新中的共享变量.现在由于某种原因,此方法产生了良好的表单响应,并且还允许后台工作人员以大约1,000倍的速度进行更新.但是,有趣的是,它只有1毫秒的精度.

因此,我们应该能够更改模式以读取当前时间并从bw线程调用更新,而无需使用计时器.

这是新模式:

//Timer setup
{
            RefreshTimer.SynchronizingObject = this;
            RefreshTimer.Elapsed += RefreshTimer_Elapsed;
            RefreshTimer.AutoReset = true;
            RefreshTimer.Start();
}           

     void bw_DoWork(object sender, DoWorkEventArgs e)
            {
                    for (int x = 0; x < 1000000000; x++)
                    {                    
                       //bw.ReportProgress(0, x);                    
                       //mUiContext.Post(UpdateLabel, x);
                        SharedX = x;
                    }
            }

        void RefreshTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            label1.Text = SharedX.ToString();
        }

更新,在这里,我们有了新的解决方案,它不需要计时器,也不会阻塞线程!通过这种模式,我们在更新的计算和保真度方面实现了高性能.不幸的是,滴答滴答TickCount的准确度仅为1 MS,但是我们可以为每个MS运行一批X更新,以获得比1 MS更快的计时.

   void bw_DoWork(object sender, DoWorkEventArgs e)
    {
            long lastTickCount = Environment.TickCount;                
            for (int x = 0; x < 1000000000; x++)
            {
                if (Environment.TickCount - lastTickCount > 1)
                {
                    bw.ReportProgress(0, x);
                    lastTickCount = Environment.TickCount;
                }                 
            }
    }

解决方案

尝试以比用户跟踪进度更快的速度报告进度没有什么意义.

如果您的后台线程发布消息的速度超过了GUI处理消息的速度(并且您有所有的症状-GUI对用户输入的共鸣不佳,DoEvents失控递归),则您必须以某种方式限制进度更新. /p>

一种常见的方法是使用主线程形式计时器以足够小的速率来更新GUI,以使用户看到可接受的进度读数.您可能需要一个互斥或关键部分来保护共享数据,但是如果要监视的进度值是int/uint,则不需要此amy.

另一种选择是通过强制线程在事件或信号量上阻塞直到GUI空闲来勒死线程.

Let me setup this question with some background information, we have a long running process which will be generating data in a Windows Form. So, obviously some form of multi-threading is going to be needed to keep the form responsive. But, we also have the requirement that the form updates as many times per second while still remaining responsive.

Here is a simple test example using background worker thread:

void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        int reportValue = (int)e.UserState;
        label1.Text = reportValue;
        //We can put this.Refresh() here to force repaint which gives us high repaints but we lose
        //all other responsiveness with the control

    }

    void bw_DoWork(object sender, DoWorkEventArgs e)
    {
            for (int x = 0; x < 100000; x++)
            {     
              //We could put Thread.Sleep here but we won't get highest performance updates
                bw.ReportProgress(0, x);                    
            }
    }

Please see the comments in the code. Also, please don't question why I want this. The question is simple, how do we achieve the highest fidelity (most repaints) in updating the form while maintaining responsiveness? Forcing the repaint does give us updates but we don't process windows messages.

I have also try placing DoEvents but that produces stack overflow. What I need is some way to say, "process any windows messages if you haven't lately". I can see also that maybe a slightly different pattern is needed to achieve this.

It seems we need to handle a few issues:

  1. Updating the Form through the non UI thread. There are quite a few solution to this problem such as invoke, synchronization context, background worker pattern.
  2. The second problem is flooding the Form with too many updates which blocks the message processing and this is the issue around which my question really concerns. In most examples, this is handles trivially by slowing down the requests with an arbitrary wait or only updating every X%. Neither of these solutions are approriate for real-world applications nor do they meet the maximum update while responsive criteria.

Some of my initial ideas on how to handle this:

  1. Queue the items in the background worker and then dispatch them in a UI thread. This will ensure every item is painted but will result in lag which we don't want.
  2. Perhaps use TPL
  3. Perhaps use a timer in the UI thread to specify a refresh value. In this way, we can grab the data at the fastest rate that we can process. It will require accessing/sharing data across threads.


Update, I've updated to use a Timer to read a shared variable with the Background worker thread updates. Now for some reason, this method produces a good form response and also allows the background worker to update about 1,000x as fast. But, interestingly it only 1 millisecond accurate.

So we should be able to change the pattern to read the current time and call the updates from the bw thread without the need for the timer.

Here is the new pattern:

//Timer setup
{
            RefreshTimer.SynchronizingObject = this;
            RefreshTimer.Elapsed += RefreshTimer_Elapsed;
            RefreshTimer.AutoReset = true;
            RefreshTimer.Start();
}           

     void bw_DoWork(object sender, DoWorkEventArgs e)
            {
                    for (int x = 0; x < 1000000000; x++)
                    {                    
                       //bw.ReportProgress(0, x);                    
                       //mUiContext.Post(UpdateLabel, x);
                        SharedX = x;
                    }
            }

        void RefreshTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            label1.Text = SharedX.ToString();
        }

Update And here we have the new solution that doesn't require the timer and doesn't block the thread! We achieve a high performance in calculations and fidelity on the updates with this pattern. Unfortunately, ticks TickCount is only 1 MS accurate, however we can run a batch of X updates per MS to get faster then 1 MS timing.

   void bw_DoWork(object sender, DoWorkEventArgs e)
    {
            long lastTickCount = Environment.TickCount;                
            for (int x = 0; x < 1000000000; x++)
            {
                if (Environment.TickCount - lastTickCount > 1)
                {
                    bw.ReportProgress(0, x);
                    lastTickCount = Environment.TickCount;
                }                 
            }
    }

解决方案

There is little point in trying to report progress any faster than the user can keep track of it.

If your background thread is posting messages faster than the GUI can process them, (and you have all the symtoms of this - poor GUI resonse to user input, DoEvents runaway recursion), you have to throttle the progress updates somehow.

A common approach is to update the GUI using a main-thread form timer at a rate sufficiently small that the user sees an acceptable progress readout. You may need a mutex or critical section to protect shared data, though that amy not be necessary if the progress value to be monitored is an int/uint.

An alternative is to strangle the thread by forcing it to block on an event or semaphore until the GUI is idle.

这篇关于Winforms更新具有高性能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆