Reputation: 4321
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
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