开发者

WPF: How to call Dispatcher.BeginInvoke *only* when there is nothing on the queue to be called?

开发者 https://www.devze.com 2023-01-31 13:00 出处:网络
I have an import file method in a WPF app that reads a file and inserts some records in a DB. This method runs in a BackgroundWorker object.

I have an import file method in a WPF app that reads a file and inserts some records in a DB.

This method runs in a BackgroundWorker object. I have a progress bar being updated inside a Dispatcher.Invoke call. If I run as is, it takes ~1 minute to import 200k records, if I just don't show any progress, it takes just 4 to 5 seconds! And if I use Dispatcher.BeginInvoke with Background priority, it takes the same 4 to 5 seconds, but the progress bar + a counter are being updated and takes ~1 minute. So, obviu开发者_如何学Gosly, the UI is the problem here.

And the other problem is that I need to show a progress, so I was thinking if there is any way to use Dispatcher.BeginInvoke but first check if there is anything on the queue and if so, I just skip it, which would behave like: in the 1st second, 1% done, 2 secs later 50% done and in the 4th second 100% done).

Any help on this?

thanks!!!


The problem is that your callbacks are queuing up on the Dispatcher. Each one will cause the screen to repaint, and because they are at Background priority the next one will wait for that repaint to complete before being processed, so you will have to repaint once per callback, which can be slow.

Instead of trying to wait until nothing at all is in the dispatcher queue, just wait until the previous progress callback has been handled before posting a new one. This will ensure you never have more than one active at a time, so they can't queue up.

You can do this by setting a flag when you post the callback and clearing it once it has been processed. For example:

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    var pending = false;
    for (int i = 0; i < 1000000; i++)
    {
        // Do some work here
        // ...
        // Only report progress if there is no progress report pending
        if (!pending)
        {
            // Set a flag so we don't post another progress report until
            // this one completes, and then post a new progress report
            pending = true;
            var currentProgress = i;
            Dispatcher.BeginInvoke(new Action(() =>
            {
                // Do something with currentProgress
                progressBar.Value = currentProgress;
                // Clear the flag so that the BackgroundWorker
                // thread will post another progress report
                pending = false;
            }), DispatcherPriority.Background);
        }
    }
}


I would simply update a progress counter in the background thread (it only writes to the counter), and have the UI read (only read) the timer every 500 ms or so... There is no reason to update faster than that. Also, because one thread is write only, and one is read only there is no threading issues required. The code becomes massively simpler, cleaner, and more maintainable.

-Chert Pellett


Impossible to say without seeing code, but

I have a progress bar being updated inside a Dispatcher.Invoke call

Why? That's what ReportProgress is for.

If I had to guess (and I do), I'd say you're reporting progress to often. For example, don't report progress after every record, but after batches of 100 or whatever.


I just solved the same case, but using the object returned by BeginInvoke, and I think it’s quite elegant too!

DispatcherOperation uiOperation = null;
while (…)
{
    …
    if (uiOperation == null || uiOperation.Status == DispatcherOperationStatus.Completed || uiOperation.Status == DispatcherOperationStatus.Aborted)
    {
        uiOperation = uiElement.Dispatcher.BeginInvoke(…);
    }
}

The progress bars become a little choppier (less smooth), but it flies. In my case, the code parses line-by-line from a text file using StreamReader.ReadLine(). Updating the progress bar after reading every line would cause the read operations to complete before the progress bar was even halfway filled. Using the synchronous Dispatcher.Invoke(…) would slow down the entire operation to 100 KiB/s, but the progress bar would accurately track the progress. Using the solution above, my application finished parsing 8,000 KiB in a second with just 3 progress bar updates.

One difference from using BackgroundWorker.ReportProgress(…) is that the progress bar can show finer detail in longer-running operations. BackgroundWorker.ReportProgress(…) is limited to reporting progress in increments of 1% from 0% to 100%. If your progress bar represents more than 100 operations, finer values are desirable. Of course, that could also be achieved by not using the percentProgress argument and passing in a userState to BackgroundWorker.ReportProgress(…) instead.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号