Send a backgroundworker to sleep while checking for cancellation

14,123

Solution 1

You're going down the wrong path.

  • A Bgw is using the ThreadPool and should therefore be used for (relatively) short actions, typically under 0.5 seconds. Keeping one around indefinitely is not recommended.
  • You should almost never use Sleep() on any thread. Certainly not for more than a few ms.
  • using Sleep(1) in a busy-wait for 5 seconds is a big waste of CPU. You are hurting your other threads. At the very least consider upping it to Sleep(100) or so. Find the max delay you will allow for your Cancel.

As a solution: Use a Timer. You have a periodical task, use the right tool.

Use System.Threading.Timer for ThreadPool based background work. Update the GUI through Dispatcher.Invoke().

Use the Dispatcher.Timer to execute small (under 0.1 sec) portions of work on the Main thread. You can directly update the GUI.

Solution 2

You can use a ManualResetEvent to both allow your thread to wait without polling and to allow another thread to wake it up any anytime.

The ManualResetEvent as well as any of the other thread synchronization objects has a WaitOne method that can wait for the event to be set or for a time out. So normally you can have WaitOne wait for the amount you want to wait for (which will work like a Thread.Sleep) but the main thread can wake up the background thread at any time.

ManualResetEvent backgroundWakeEvent = new ManualResetEvent(false);
....
bkgwk.CancelAync();
backgroundWaitEvent.Set();
....
backgroundWakeEvent.WaitOne(5000);

if (bkgwk.CancellationPending)
{
    cancelled = true;
    e.Cancel = true;
    bkgwk.Dispose();
    break;
}

Solution 3

Or even better, use the Reactive Extensions framework and have a "worker" observe a stream of interval events. The stream can then easily be disposed to stop generating new events, thus terminating the worker. Combine this with a cancellation disposable and you have one single disposable to stop both the interval timer and any worker doing its work:

var cancel = new BooleanDisposable();
var subscription = Observable.Interval(TimeSpan.FromSeconds(20))
                             .ObserveOn(Scheduler.DispatcherScheduler)
                             .Subscribe(i => PerformWorkOnUI(cancel));
var uiWork = new CompositeDisposable(cancel, subscription);

To cancel the stream of timer events, you only have to dispose the composite cancellation/subscription:

uiWork.Dispose();

Clarification: ObserveOn(Scheduler.DispatcherScheduler) ensures that the subscription will be called on the dispatcher thread.

Share:
14,123
Damo
Author by

Damo

Learning c#

Updated on June 04, 2022

Comments

  • Damo
    Damo almost 2 years

    I have a background worker which updates the GUI on a regular basis via ReportProgress.

    The update occurs at regular intervals, every 5 seconds for example, or it could be 20 seconds. In order to perform the update at set times I send the worker process to sleep for the duration and when it wakes it updates the GUI with new information.

    The worker supports cancellation, and outside of sleeping it cancels correctly.

    I want to be able to invoke the cancellation during the wait period, however sending the thread to sleep makes this impossible.

    I assume I'll have to invoke a loop and check for the cancellation as part of the loop to simulate the thread sleep.

    What is the best method for achieving this, my timing is completely off with my attempt.

                long counter = 0;
                long sleepfor = timelinespeed*1000;
                int timelinespeed = 10;
    
                    while (counter != sleepfor)
                    {
    
                        Thread.Sleep(1);
    
                        counter++;
    
                        if (bkgwk.CancellationPending)
                        {
                            cancelled = true;
                            e.Cancel = true;
                            bkgwk.Dispose();
                            break;
                        }
    
                    }
    
  • JMarsch
    JMarsch about 12 years
    +1 for timer suggestion. If you are using wpf, you might also look at the Dispatcher timer. Cancellation becomes easy -- you just stop the timer (assuming that it's ok to allow any current task to finish)
  • Damo
    Damo about 12 years
    Does System.Threading.Timer allow me to update the GUI with information? MSDN suggests a System.Windows.Forms.Timer, however this is single threaded?
  • Henk Holterman
    Henk Holterman about 12 years
    Both the Forms.Timer and Dispatcher.Timer run on the main Thread, so I would not use them for heavy operations. Updating the GUI from every other Thread is the same: use Dispatcher.Invoke()