Eae
Eae

Reputation: 4321

Cancelling a BackgroundWorker

My DoWork for backgroundworker1 sets a wait timer via a class WakeUp, it works well.

The problem right now is that sometimes my while CancellationPending is ending in an infinite loop. So inside DoWork there is a call to WaitOne, the DoWork sets the wait timer stuff and waits the thread until the timer triggers.

I need for the BackgroundWorker to shut down right away but as well I have to keep a reference to BackgroundWorker so I can keep track of each alarm in my program. Why is CancellationPending taking so long? It never seems to finish. Is there some way to kill off the backgroundworker without having to wait like this in a while loop for so long?

switch (alarmNum)
{
    case 1:
                WakeUp.CancelWakeUp(threadHandles[removal]);
        if(backgroundworker1.IsBusy)
        {

            backgroundworker1.CancelAsync();
        }

        while(backgroundworker1.CancellationPending)
        {

        }

        backgroundworker1.RunWorkerAsync();
        break;
    case 2:
        if (backgroundworker2.IsBusy)
        {
            backgroundworker2.CancelAsync();
        }
        backgroundworker2.RunWorkerAsync();
        break;
    case 3:
        if (backgroundworker3.IsBusy)
        {
            backgroundworker3.CancelAsync();
        }
        backgroundworker3.RunWorkerAsync();
        break;
    case 4:
        if (backgroundworker4.IsBusy)
        {
            backgroundworker4.CancelAsync();
        }
        backgroundworker4.RunWorkerAsync();
        break;
    case 5:
        if (backgroundworker5.IsBusy)
        {
            backgroundworker5.CancelAsync();
        }
        backgroundworker5.RunWorkerAsync();
        break;
}



    private void bw1_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;


        if (worker.CancellationPending == true)
        {
          e.Cancel = true;
        }

        WakeUp temp = new WakeUp("spalarm1");
        threadHandles[0] = temp.tHandle;
        temp.initWakeUp(dtCurSpan);

        //****************************
              It's blocking here -< <--
              temp.DoWork sets a system timer so its blocking
              with a call to WaitOne inside WakeUp. The timer I'm setting
              is called a waitable timer so its blocking since the system is
              waiting for it to expire so the thread can end
           *******************************/
        temp.DoWork();
    }

WakeUp.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Windows.Controls;
using System.Threading.Tasks;
using System.Threading;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Diagnostics;

namespace WpfApplication1
{
    class WakeUp
    {
        public delegate void TimerCompleteDelegate(IntPtr complretionArg,
                                               UInt32 timerLow, UInt32 timerHigh);

        public SafeWaitHandle tHandle;
        bool rslt;

        //Various imports of kernel32.dll so the waitable timer can be set
        //on the system
        [DllImport("kernel32.dll")]
        public static extern SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes, bool bManualReset, string lpTimerName);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetWaitableTimer(SafeWaitHandle hTimer, [In] ref long pDueTime, int lPeriod, IntPtr pfnCompletionRoutine, IntPtr lpArgToCompletionRoutine, bool fResume);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool CancelWaitableTimer(SafeWaitHandle hTimer);

        //SafeHandle.DangerousGetHandle Method
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CloseHandle(IntPtr hObject);

        //The constructor will use a TimeSpan to set a waitable timer
        public WakeUp(string wtName)
        {
            tHandle = CreateWaitableTimer(IntPtr.Zero, true, wtName);
        }

        public int initWakeUp(TimeSpan smParam)
        {
            //The number of ticks needs to be negated to set a waitable timer in this fashion
            long waketicks = -smParam.Ticks;
            rslt = SetWaitableTimer(tHandle, ref waketicks, 0, IntPtr.Zero, IntPtr.Zero, true);

            if(!rslt)
            {
                return Marshal.GetLastWin32Error();
            }
            else
            {
                return 0;
            }
        }

        private static Exception GetWin32Exception()
        {
            return Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
        }

        public int DoWork()
        {
            using (EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset))
            {
                wh.SafeWaitHandle = tHandle;
                wh.WaitOne();
                Thread.Sleep(5000);
            }

            return 0;
        }



        //This function needs to check the return value of
        //CancelWaitableTimer
        static public void CancelWakeUp(SafeWaitHandle clHandle)
        {
            CancelWaitableTimer(clHandle);
        }
    }
}

Possible solution:

case 1:
WakeUp.CancelWakeUp(threadHandles[removal]);

threadHandles[removal] = null;
backgroundworker1.CancelAsync();
backgroundworker1.Dispose();

backgroundworker1 = new BackgroundWorker();
backgroundworker1.WorkerReportsProgress = true;
backgroundworker1.WorkerSupportsCancellation = true;
backgroundworker1.DoWork += new DoWorkEventHandler(bw1_DoWork);
backgroundworker1.ProgressChanged += new ProgressChangedEventHandler(bw1_ProgressChanged);
backgroundworker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw1_RunWorkerCompleted);

backgroundworker1.RunWorkerAsync();
break;

but I was trying to avoid this type of code.

Upvotes: 0

Views: 4093

Answers (1)

Kirill Shlenskiy
Kirill Shlenskiy

Reputation: 9587

I am not familiar with the kernel32 APIs that you're PInvoking, so I'll give you a basic example using System.Threading.Timer (which can be reset after a callback has been scheduled).

EDIT

Changed timer logic to suppress timer callback on cancellation in order to bring the example closer to your scenario.

using System;
using System.ComponentModel;
using System.Threading;

void BackgroundWorkerTimerCancellation()
{
    var worker = new BackgroundWorker { WorkerSupportsCancellation = true };

    // Cancellation support.
    var cts = new CancellationTokenSource();
    var cancellationToken = cts.Token;

    // Cancel worker when the CancellationTokenSource is canceled.
    cancellationToken.Register(worker.CancelAsync);

    // This ManualResetEvent will allow us
    // to block until DoWork has finished
    // (testing purposes only).
    using (var outerMRE = new ManualResetEvent(false))
    {
        worker.DoWork += delegate
        {
            try
            {
                // Mimics your EventWaitHandle.
                using (var innerMRE = new ManualResetEvent(false))
                {
                    // Our timer which takes a looong time.
                    using (var timer = new Timer(_ => innerMRE.Set(), null, 10000 /* 10 seconds */, Timeout.Infinite))
                    {
                        // Wire cancellation.
                        cancellationToken.Register(() =>
                        {
                            // Cancel the timer (callback will never execute).
                            timer.Change(Timeout.Infinite, Timeout.Infinite);

                            // Signal wait handle immediately when canceled.
                            // It's lack of this call which is making
                            // your BackgroundWorker run indefinitely
                            // when the timer is canceled.
                            innerMRE.Set();
                        });

                        // Block until timer callback runs or until
                        // the CancellationTokenSource is canceled.
                        innerMRE.WaitOne();
                    }
                }
            }
            finally
            {
                // Allow the outerMRE.WaitOne() call to complete.
                outerMRE.Set();
            }
        };

        // Start the worker (non-blocking).
        worker.RunWorkerAsync();

        // Schedule auto-cancellation after 1 second (non-blocking).
        cts.CancelAfter(TimeSpan.FromSeconds(1));

        // Block until DoWork has finished.
        // Will take 10 seconds without cancellation,
        // or one second with cancellation.
        outerMRE.WaitOne();
    }
}

Upvotes: 2

Related Questions