Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
736 views
in Technique[技术] by (71.8m points)

multithreading - Winforms updates with high performance

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;
                }                 
            }
    }
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...