Control.BeginInvoke does not call delegate when UI function is idle - c#

I am modifying a windows desktop application that works with some external hardware. When the user activates the hardware from the application a progress (UI) form is started. This form creates a thread that performs all of the work with the hardware. The problem comes when I try to report progress back to the UI thread. It appears that the first of my Control.BeginInvoke ("Negotiating message") works fine. However, the second one (first adjustment to progressbar) never seems to call it's delegate and as a result the application locks up on the subsequent endinvoke. I believe the issue is that the GUI is now in an idle state, but I am not sure how to fix the situation. Any help would be appreciated. Code found below:
In the UI Load Method Thread:
private void frmTwainAquire_Load(object sender, EventArgs e)
{
try
{
//Show the GUI
this.Visible = showGUI;
pbScanningProgress.Value = 0;
btnCancel.Enabled = false;
btnCancel.Visible = false;
// Set the delegates.
SetScanMessageDelegate = new SetScanMessage(this.SetScanMessageMethod);
SetRegistrationMessageDelegate = new SetRegistrationMessage(this.SetRegistrationMessageMethod);
AddScanProgressDelegate = new AddScanProgress(this.AddScanProgressMethod);
AddRecogProgressDelegate = new AddRecogProgress(this.AddRecogProgressMethod);
// Set progress bars.
pbScanningProgress.Value = 0;
pbRecognition.Value = 0;
abortScan = false;
// Create thread here!
twainInstance = new rScan.Twain();
rScanning = new rScanThread(this, twainInstance);
// Start the thread.
rScanning.tScan = new Thread(rScanning.Scan);
rScanning.tScan.Start();
}
catch (Exception ex)
{
// Error checking here.
}
}
Delegate Methods:
public void SetScanMessageMethod(string scanMessage)
{
this.lblScanMessage.Text = scanMessage;
}
public void SetRegistrationMessageMethod(string recogMessage)
{
this.lblRecognition.Text = recogMessage;
}
public void AddScanProgressMethod(int progress)
{
this.pbScanningProgress.Value += progress;
}
public void AddRecogProgressMethod(int progress)
{
this.pbRecognition.Value += progress;
}
Thread method that is giving the problem. Please note that the thread is in a different class then the previous two code blocks (both are in the UI class):
public class rScanThread : IMessageFilter
public void Scan()
{
// Set progress bar message.
IAsyncResult result;
if (frmTwainAquireInstance.lblScanMessage.IsHandleCreated && frmTwainAquireInstance.lblScanMessage.InvokeRequired)
{
result = frmTwainAquireInstance.lblScanMessage.BeginInvoke(frmTwainAquireInstance.SetScanMessageDelegate, "Negotiating Capabilities with Scanner.");
frmTwainAquireInstance.lblScanMessage.EndInvoke(result);
}
else
{
frmTwainAquireInstance.lblScanMessage.Text = "Negotiating Capabilities with Scanner.";
}
// Start the intialization of the rScan process.
bool intializeSuccess = twainInstance.Initialize(frmTwainAquireInstance.Handle);
// If the process could not be started then quit.
if (!intializeSuccess)
{
frmTwainAquireInstance.Close();
return;
}
if (frmTwainAquireInstance.pbScanningProgress.IsHandleCreated && frmTwainAquireInstance.pbScanningProgress.InvokeRequired)
{
result = frmTwainAquireInstance.pbScanningProgress.BeginInvoke(frmTwainAquireInstance.AddScanProgressDelegate, 33);
frmTwainAquireInstance.pbScanningProgress.EndInvoke(result); // Lock up here.
}
else
{
frmTwainAquireInstance.pbScanningProgress.Value += 33;
}
// Do more work after. The code never makes it this far.
} // End of rScanThread.Scan()

Related

Allow main (gui) thread while doing a task then update table

I'm using a winform to try to gather online/offline status of every IP on the network, doing so I'm using a ping request, if it replies it marks the IP as online then moves on. Only issue is waiting for up to 255 replies, after it gets all 255 responses I am wanting it to fill a data grid view.
I've managed to get it all to work but only downside is, gui hangs while doing this process, I figured just use a separate thread with the following expression
Thread T1 = new Thread(PingHost)
T1.run();
PingHost does as the name implies, pings all the hosts and decides if online or offline. Problem is I tried to update the dgv table from the thread, naturally dgv is locked to main thread.
So I tried switching to a Task and just grab the return value and update after everything is finished. Sadly I can't quite get how to get the return value from the task AND have it run on a separate thread.
I've tried googling different methods, but just running in circles at this point, so I humbly come to you guys for assistance
Code of main thread using tasks
List<string> everything = new List<string>();
int i = 0;
Task<List<string>> task1 = Task<List<string>>.Factory.StartNew(PingHost);
everything = task1.Result;
foreach(var item in everything)
{
var index = dataGridView1.Rows.Add();
dataGridView1.Rows[i].Cells["IP"].Value = item;
i++;
}
And this is the thread of my PingHost method
bool pingable = false;
Ping pinger = null;
int i = 0;
string ip;
while (i < 255)
{
ip = "192.168.0." + i;
try
{
pinger = new Ping();
PingReply reply = pinger.Send(ip, 8);
pingable = reply.Status == IPStatus.Success;
}
catch (PingException)
{
MessageBox.Show("ERROR");
// Discard PingExceptions and return false;
}
finally
{
if (pinger != null)
{
pinger.Dispose();
}
}
if (pingable)
{
checkedIP.Add(ip + ": ONLINE");
}
else
{
checkedIP.Add(ip + ": OFFLINE");
}
i++;
}
return checkedIP;
This might be a bit overkill, but I just drafted a solution. Basically I created a new Class for pinging with an event that triggers after each Ping returned, this event uses custom EventArgs to return the IP that was just pinged and if it is online or not. I then subscribed to that Event in my GUI Thread and just update the GUI. Here's some code:
This is my Pinger class responsible for pinging the actual Computers:
class Pinger
{
public event EventHandler<PingReturnedEventArgs> OnPingReturned;
public void PingNetwork()
{
for (int i = 1; i <= 255; i++)
{
string ip = $"192.168.0.{i}";
Ping ping = new Ping();
try
{
PingReply reply = ping.Send(IPAddress.Parse(ip));
TriggerEvent(reply?.Address.ToString(), true);
}
catch (Exception)
{
TriggerEvent(ip, false);
}
}
}
private void TriggerEvent(string ip, bool online)
{
if (OnPingReturned == null) return;
PingReturnedEventArgs args = new PingReturnedEventArgs(ip, online);
OnPingReturned(this, args);
}
}
My custom EventArgs:
class PingReturnedEventArgs : EventArgs
{
public string Ip { get; private set; }
public bool Online { get; private set; }
private PingReturnedEventArgs() { }
public PingReturnedEventArgs(string ip, bool online)
{
Ip = ip;
Online = online;
}
}
And finally here is how I'm actually using all of this:
Pinger pinger = new Pinger();
pinger.OnPingReturned += Your event handler to update the GUI
// I recommend doing it like this so you can stop the Thread at a later time
// Maybe with like a cancel button
Thread pingThread = new Thread(pinger.PingNetwork);
pingThread.Start();
The event handler looks like this private void PingReturnedHandler(object sender, PingReturnedEventArgs args)
The two main benefits of this are that 1. the GUI Thread remains unblocked, meaning the GUI will still respond to user input and 2. this procedurally (on every ping completion) triggers the event, meaning that if it takes a long time to ping all the PCs you don't have to wait for the entirety to finish before the user sees something

Windows Service: The calling thread cannot access this object because a different thread owns it

Trying to convert XML files using XSL and printing the output. However, receiving the following message: The calling thread cannot access this object because a different thread owns it.
To set an interval for checking files, added a timer to the OnStart.
if (findPrinter() > 0)
{
System.Timers.Timer printNetterCheck = new System.Timers.Timer();
printNetterCheck.Elapsed += new ElapsedEventHandler(OnTimedEvent);
printNetterCheck.Interval = 30000;
printNetterCheck.Enabled = true;
}
The OnTimedEvent:
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
getFiles();
}
If any files available, call print:
foreach (string file in files)
{
try
{
StringWriter xslTransformResult = new StringWriter();
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load(xslPath);
xslt.Transform(file, null, xslTransformResult);
if (print(xslTransformResult) == 1)
{
//do some things
The print function:
private int print(StringWriter transformedXML)
{
//assume OK
int rc = 1;
try
{
StringReader printNetterStreamReader = new StringReader(transformedXML.ToString());
PrintSystemJobInfo printNetterJob = printer.AddJob("PrintNetterPrint");
Stream printNetterStream = printNetterJob.JobStream;
Byte[] printNetterByteBuffer = UnicodeEncoding.Unicode.GetBytes(printNetterStreamReader.ReadToEnd());
printNetterStream.Write(printNetterByteBuffer, 0, printNetterByteBuffer.Length);
printNetterStream.Close();
}
catch (Exception e)
{
//return fail
rc = -1;
eventLog.WriteEntry("Error printing: " + e.Message, EventLogEntryType.Error);
}
return rc;
}
When calling print I receive the thread error. Found some stuff about Dispatchers etc.. but those are not available when using services.
Check PrintQueue.AddJob.
The method makes a COM call which requires the application be running in a single apartment (STA). The easiest way to fix that is to add STAThreadAttribute to Main which will force the application to run in a single thread. If you need multithreading in your application then you will need to implement the necessary plumbing to run the PrintQueue separately on an STA Thread.
// Create a factory to hold your printer configuration
// So that it can be retrieved on demand
// You might need to move the findPrinter() logic
public class PrintQueueFactory
{
private static PrintQueue _instance = new PrinterQueue(/* Details */);
public static PrintQueue PrintQueue { get { return _instance; } }
}
private int print(StringWriter transformedXML)
{
//assume OK
int rc = 1;
try
{
var printer = PrintQueueFactory.PrintQueue;
var staThread = new Thread(() => Print(printer, transformedXML.ToString()));
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start();
staThread.Join();
}
catch (Exception e)
{
//return fail
rc = -1;
eventLog.WriteEntry("Error printing: " + e.Message, EventLogEntryType.Error);
}
return rc;
}
private static void Print(PrintQueue printer, string lines)
{
using(var printNetterJob = printer.AddJob("PrintNetterPrint"))
using(var printNetterStreamReader = new StringReader(lines))
using(var printNetterStream = printNetterJob.JobStream)
{
Byte[] printNetterByteBuffer = UnicodeEncoding.Unicode.GetBytes(printNetterStreamReader.ReadToEnd());
printNetterStream.Write(printNetterByteBuffer, 0, printNetterByteBuffer.Length);
}
}
maybe, as you are using a Timer control, it is related with multi-threading, maybe you should check if an Invoke is Required (InvokeRequired) in the Timer.Elapsed event handler.
If so, you should create a delegate to call this function, so it can be executed in the right thread.
Check this Invoke-Required question

Thread.Abort causing deadlock in catch{} statement

I have a set of threaded classes that print different types of documents. The classes use inheritance to share common code. The class constructor requires file name and printer name arguments. A Print() method creates a new worker thread, waits for the worker thread to complete using Thread.Join(timeout) and calls Thread.Abort() on the worker thread if the Join times out. The worker thread starts an application that can open the specified file, causes the file to be sent to printer synchronously (usually using application's Print method) and exits. The worker thread's code is wrapped in a try{} ... catch{} block to deal with any unforeseen crashes of the external application. The catch block contains minimal cleanup and logging.
internal static FilePackage TryPrintDocumentToPdf(string Filename)
{
.....
Logging.Log("Printing this file using PowerPoint.", Logging.LogLevel.Debug);
printableFormat = true;
fc = new FileCollector(Email2Pdf.Settings.Printer.PdfAttachmentCollectDirectoryObj, FileCollector.CollectMethods.FileCount | FileCollector.CollectMethods.FilesNotInUse | FileCollector.CollectMethods.ProcessExit);
fc.FileCount = 1;
fc.ProcessNames = new string[] { OfficePowerPointExe, Email2Pdf.Settings.Printer.PrinterExe };
fc.Prepare();
using (PowerPointPrinter printer = new PowerPointPrinter(Filename, Email2Pdf.Settings.Printer.PdfAttachmentPrinter))
{
printer.KillApplicationOnClose = true;
printer.Print();
printOk = printer.PrintOk;
}
.....
}
internal abstract class ApplicationPrinter : IDisposable
{
protected abstract string applicationName { get; }
protected string filename;
protected string printer;
protected bool workerPrintOk;
protected bool printOk;
public bool PrintOk { get { return printOk; } }
public bool KillApplicationOnClose { get; set; }
public void Print()
{
System.Threading.Thread worker = new System.Threading.Thread(printWorker);
DateTime time = DateTime.Now;
worker.Start();
if (worker.Join(new TimeSpan(0, Email2Pdf.Settings.Printer.FileGenerateTimeOutMins, 0)))
{
printOk = workerPrintOk;
}
else
{
worker.Abort();
printOk = false;
Logging.Log("Timed out waiting for " + applicationName + " file " + filename + " to print.", Logging.LogLevel.Error);
}
}
protected abstract void Close();
protected abstract void printWorker();
public virtual void Dispose() { Close(); }
}
internal class PowerPointPrinter : ApplicationPrinter
{
private const string appName = "PowerPoint";
protected override string applicationName { get { return appName; } }
private Microsoft.Office.Interop.PowerPoint.Application officePowerPoint = null;
public PowerPointPrinter(string Filename, string Printer)
{
filename = Filename;
printer = Printer;
this.Dispose();
}
protected override void printWorker()
{
try
{
officePowerPoint = new Microsoft.Office.Interop.PowerPoint.Application();
officePowerPoint.DisplayAlerts = Microsoft.Office.Interop.PowerPoint.PpAlertLevel.ppAlertsNone;
Microsoft.Office.Interop.PowerPoint.Presentation doc = null;
doc = officePowerPoint.Presentations.Open(
filename,
Microsoft.Office.Core.MsoTriState.msoTrue,
Microsoft.Office.Core.MsoTriState.msoFalse,
Microsoft.Office.Core.MsoTriState.msoFalse);
doc.PrintOptions.ActivePrinter = printer;
doc.PrintOptions.PrintInBackground = Microsoft.Office.Core.MsoTriState.msoFalse;
doc.PrintOptions.OutputType = Microsoft.Office.Interop.PowerPoint.PpPrintOutputType.ppPrintOutputSlides;
doc.PrintOut();
System.Threading.Thread.Sleep(500);
doc.Close();
//Marshal.FinalReleaseComObject(doc);
doc = null;
workerPrintOk = true;
}
catch (System.Exception ex)
{
Logging.Log("Unable to print PowerPoint file " + filename + ". Exception: " + ex.Message, Logging.LogLevel.Error);
Close();
workerPrintOk = false;
}
}
protected override void Close()
{
try
{
if (officePowerPoint != null)
officePowerPoint.Quit();
Marshal.FinalReleaseComObject(officePowerPoint);
officePowerPoint = null;
if (KillApplicationOnClose)
Utility.KillProcessesByName(OfficePowerPointExe);
}
catch { }
}
}
I found my application non-responsive, with the main thread in a Sleep/Wait/Join at the Thread.Abort() line. I do not recall the status of the worker thread, but the logging that was supposed to be performed in the catch{} block did not take place. (I Attached to my process with VS2010 after I found it non-responsive).
I refer to the following Note from the Thread.Abort Method:
The thread that calls Abort might block if the thread that is being
aborted is in a protected region of code, such as a catch block,
finally block, or constrained execution region. If the thread that
calls Abort holds a lock that the aborted thread requires, a deadlock
can occur.
I believe I have a dead-locking issue because (1) it does not always happen, and (2) because of the Note on MSDN (above).
The Note appears to suggest that using try{} ... catch{} is NEVER safe inside a thread if the thread can be Abort()'ed. Is this true?
I do not see how I can avoid using Abort() in my scenario. Will using Thread.Interrupt() instead make any difference?
How to I fix the dead-locking issue I have?
BackgroundWorker does not work for me because I do not need progress reporting and, more importantly, it is possible that my worker thread will block indefinitely when it executes third party applications. For the same reason, I cannot ask my thread to terminate, but have one option only - to ruthlessly Abort() the worker thread.
Your mechanism using Thread.Abort() is not a good one. In fact, calling Thread.Abort() should be avoided.
The thread that calls Abort might block if the thread that is being
aborted is in a protected region of code, such as a catch block,
finally block, or constrained execution region. If the thread that
calls Abort holds a lock that the aborted thread requires, a deadlock
can occur. Ref.
Instead, use a BackgroundWorker which supports cancellation, progress reporting (and auto marshalling onto UI thread in completed event).
It looks to me like you are basically remote-controlling the PowerPoint application in order to print a PowerPoint document. Thus you could be subject to any dialog boxes that the application put up (or tried to put up) on the screen. If this whole thing is being run in the background (e.g. on a server), there may not be a user to dismiss any such dialogs, so that could explain part of the issue. My recommendation would be to look into third-party libraries that would allow you to load a PPT file and print it (or convert it to PDF and print that) without having to rely on the PowerPoint application. Then you wouldn't have to wait on an external app outside your control and you woudln't have to resort to forcefully aborting threads.
I think I found a solution by making the following changes:
Do not call Thread.Abort() if we know that the worker thread is executing a catch{} block (see protected volatile bool isPrinting below).
Use a separate thread to call Thread.Abort() and encourage a context switch with Sleep(0) (see private void AbortPrintWorker() below).
internal abstract class ApplicationPrinter : IDisposable
{
protected abstract string applicationName { get; }
protected string filename;
protected string printer;
protected bool workerPrintOk;
protected bool printOk;
public bool PrintOk { get { return printOk; } }
public bool KillApplicationOnClose { get; set; }
protected System.Threading.Thread worker;
protected volatile bool isPrinting;
public void Print()
{
worker = new System.Threading.Thread(printWorker);
DateTime time = DateTime.Now;
worker.Start();
if (worker.Join(new TimeSpan(0, Email2Pdf.Settings.Printer.FileGenerateTimeOutMins, 0)))
{
printOk = workerPrintOk;
}
else
{
AbortPrintWorker();
printOk = false;
Logging.Log("Timed out waiting for " + applicationName + " file " + filename + " to print.", Logging.LogLevel.Error);
}
}
protected abstract void printWorker();
public abstract void Dispose();
private void AbortPrintWorker()
{
System.Threading.Thread abortThread = new System.Threading.Thread(abortWorker);
if (isPrinting)
{
abortThread.Start();
System.Threading.Thread.Sleep(0);
abortThread.Join();
}
else
{
worker.Join();
}
}
private void abortWorker()
{
worker.Abort();
worker.Join();
}
}
internal class PowerPointPrinter : ApplicationPrinter
{
private const string appName = "PowerPoint";
protected override string applicationName { get { return appName; } }
private Microsoft.Office.Interop.PowerPoint.Application officePowerPoint = null;
public PowerPointPrinter(string Filename, string Printer)
{
filename = Filename;
printer = Printer;
this.Dispose();
}
protected override void printWorker()
{
try
{
isPrinting = true;
officePowerPoint = new Microsoft.Office.Interop.PowerPoint.Application();
officePowerPoint.DisplayAlerts = Microsoft.Office.Interop.PowerPoint.PpAlertLevel.ppAlertsNone;
Microsoft.Office.Interop.PowerPoint.Presentation doc = null;
doc = officePowerPoint.Presentations.Open(
filename,
Microsoft.Office.Core.MsoTriState.msoTrue,
Microsoft.Office.Core.MsoTriState.msoFalse,
Microsoft.Office.Core.MsoTriState.msoFalse);
doc.PrintOptions.ActivePrinter = printer;
doc.PrintOptions.PrintInBackground = Microsoft.Office.Core.MsoTriState.msoFalse;
doc.PrintOptions.OutputType = Microsoft.Office.Interop.PowerPoint.PpPrintOutputType.ppPrintOutputSlides;
doc.PrintOut();
System.Threading.Thread.Sleep(500);
doc.Close();
Marshal.FinalReleaseComObject(doc);
doc = null;
workerPrintOk = true;
isPrinting = true;
}
catch (System.Exception ex)
{
isPrinting = false;
Logging.Log("Unable to print PowerPoint file " + filename + ". Exception: " + ex.Message, Logging.LogLevel.Error);
workerPrintOk = false;
}
}
public override void Dispose()
{
try
{
if (officePowerPoint != null)
officePowerPoint.Quit();
Marshal.FinalReleaseComObject(officePowerPoint);
officePowerPoint = null;
if (KillApplicationOnClose)
Utility.KillProcessesByName(OfficePowerPointExe);
}
catch { }
}
}
AbortPrintWorker() creates a separate thread to call Abort() on the worker thread. I believe this deals with the issue highlighted in the Note on Abort():
The thread that calls Abort might block if the thread that is being
aborted is in a protected region of code, such as a catch block,
finally block, or constrained execution region. If the thread that
calls Abort holds a lock that the aborted thread requires, a deadlock
can occur.
Is this correct?

timer in thread problem

I have my application in which in three datagridview independently in three thread load data from wcf service. I execute in each thread timer which every second load this data.
My problem is that every time my thread go threw each thread but only like I show in method timerNowyYork_Elapsed
Any idea why this happens ? I bad lock thread?
this code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Threading;
namespace Sprawdzanie_warunków_pogodowych
{
public partial class Form1 : Form
{
PogodaEntities entity = new PogodaEntities();
System.Timers.Timer timerKrakow = new System.Timers.Timer();
System.Timers.Timer timerSzczecin = new System.Timers.Timer();
System.Timers.Timer timerNowyYork = new System.Timers.Timer();
KeyValuePair<string, string> krakowInfo;
KeyValuePair<string, string> szczecinInfo;
KeyValuePair<string, string> nowyYorkInfo;
public Form1()
{
System.Net.ServicePointManager.Expect100Continue = false;
InitializeComponent();
List<MiastoContainer> miasta = (from miasto in entity.Miasta
select new MiastoContainer()
{
MiastoName = miasto.Nazwa,
Panstwo = miasto.Państwo
}).ToList();
krakowInfo = new KeyValuePair<string, string>(miasta[0].MiastoName, miasta[0].Panstwo);
szczecinInfo = new KeyValuePair<string, string>(miasta[1].MiastoName, miasta[1].Panstwo);
nowyYorkInfo = new KeyValuePair<string, string>(miasta[2].MiastoName, miasta[2].Panstwo);
ParameterizedThreadStart ptsKrakow = new ParameterizedThreadStart(PobierzKrakow);
Thread tKrakow = new Thread(ptsKrakow);
tKrakow.Start(this.dataGridViewKrakow);
ParameterizedThreadStart ptsSzczecin = new ParameterizedThreadStart(PobierzSzczecin);
Thread tSzczecin = new Thread(ptsSzczecin);
tSzczecin.Start(this.dataGridViewSzczecin);
}
private void oAutorzeToolStripMenuItem_Click(object sender, EventArgs e)
{
new AboutBox1().Show();
}
private void zapiszRaportToolStripMenuItem_Click(object sender, EventArgs e)
{
}
public void PobierzKrakow(object parameters)
{
this.timerKrakow.Elapsed += new System.Timers.ElapsedEventHandler(timerKrakow_Elapsed);
this.timerKrakow.Enabled = true;
this.timerKrakow.Interval = 1000;
this.timerKrakow.Start();
}
public void PobierzSzczecin(object parameters)
{
this.timerSzczecin.Elapsed += new System.Timers.ElapsedEventHandler(timerSzczecin_Elapsed);
this.timerSzczecin.Enabled = true;
this.timerSzczecin.Interval = 1000;
this.timerSzczecin.Start();
}
public void PobierzNowyYork(object parameters)
{
this.timerNowyYork.Elapsed += new System.Timers.ElapsedEventHandler(timerNowyYork_Elapsed);
this.timerNowyYork.Enabled = true;
this.timerNowyYork.Interval = 1000;
this.timerNowyYork.Start();
}
void timerNowyYork_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{ GlobalWeather.Weather weather = new GlobalWeather.Weather();
lock (weather)
{
//thread always start from here
List<object> weatherList = new List<object>();
weatherList.Add(weather.GetTempreature(nowyYorkInfo.Key, nowyYorkInfo.Value));
//and end here , never come any line further
weatherList.Add(weather.GetPressure(nowyYorkInfo.Key, nowyYorkInfo.Value));
weatherList.Add(weather.GetHumidity(nowyYorkInfo.Key, nowyYorkInfo.Value));
weatherList.Add(weather.GetVisibility(nowyYorkInfo.Key, nowyYorkInfo.Value));
entity.SaveChanges();
WarunkiPogodowe warunki = new WarunkiPogodowe()
{
Temperatura = weatherList[0].ToString(),
Ciśnienie = weatherList[1].ToString(),
Wilgotność = weatherList[2].ToString(),
Widoczność = weatherList[3].ToString(),
DataSprawdzenia = DateTime.Now
};
entity.AddToWarunkiPogodowe(warunki);
entity.SaveChanges();
int miastoId = entity.Miasta.First(m => m.Nazwa == nowyYorkInfo.Key).id;
Miasto_has_WarunkiPogodowe m_has_wp = new Miasto_has_WarunkiPogodowe()
{
idMiasto_FK = miastoId,
idWarunkiPogodowe_FK = warunki.id
};
entity.AddToMiasto_has_WarunkiPogodowe(m_has_wp);
entity.SaveChanges();
this.dataGridViewNowyYork.Rows.Add(warunki);
}
}
void timerSzczecin_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
GlobalWeather.Weather weather = new GlobalWeather.Weather();
lock (weather)
{
List<object> weatherList = new List<object>();
weatherList.Add(weather.GetTempreature(szczecinInfo.Key, szczecinInfo.Value));
weatherList.Add(weather.GetPressure(szczecinInfo.Key, szczecinInfo.Value));
weatherList.Add(weather.GetHumidity(szczecinInfo.Key, szczecinInfo.Value));
weatherList.Add(weather.GetVisibility(szczecinInfo.Key, szczecinInfo.Value));
entity.SaveChanges();
WarunkiPogodowe warunki = new WarunkiPogodowe()
{
Temperatura = weatherList[0].ToString(),
Ciśnienie = weatherList[1].ToString(),
Wilgotność = weatherList[2].ToString(),
Widoczność = weatherList[3].ToString(),
DataSprawdzenia = DateTime.Now
};
entity.AddToWarunkiPogodowe(warunki);
entity.SaveChanges();
int miastoId = entity.Miasta.First(m => m.Nazwa == szczecinInfo.Key).id;
Miasto_has_WarunkiPogodowe m_has_wp = new Miasto_has_WarunkiPogodowe()
{
idMiasto_FK = miastoId,
idWarunkiPogodowe_FK = warunki.id
};
entity.AddToMiasto_has_WarunkiPogodowe(m_has_wp);
entity.SaveChanges();
this.dataGridViewSzczecin.Rows.Add(warunki);
}
}
void timerKrakow_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
GlobalWeather.Weather weather = new GlobalWeather.Weather();
lock (weather)
{
List<object> weatherList = new List<object>();
weatherList.Add(weather.GetTempreature(krakowInfo.Key, krakowInfo.Value));
weatherList.Add(weather.GetPressure(krakowInfo.Key, krakowInfo.Value));
weatherList.Add(weather.GetHumidity(krakowInfo.Key, krakowInfo.Value));
weatherList.Add(weather.GetVisibility(krakowInfo.Key, krakowInfo.Value));
entity.SaveChanges();
WarunkiPogodowe warunki = new WarunkiPogodowe()
{
Temperatura = weatherList[0].ToString(),
Ciśnienie = weatherList[1].ToString(),
Wilgotność = weatherList[2].ToString(),
Widoczność = weatherList[3].ToString(),
DataSprawdzenia = DateTime.Now
};
entity.AddToWarunkiPogodowe(warunki);
entity.SaveChanges();
int miastoId = entity.Miasta.First(m => m.Nazwa == krakowInfo.Key).id;
Miasto_has_WarunkiPogodowe m_has_wp = new Miasto_has_WarunkiPogodowe()
{
idMiasto_FK = miastoId,
idWarunkiPogodowe_FK = warunki.id
};
entity.AddToMiasto_has_WarunkiPogodowe(m_has_wp);
entity.SaveChanges();
this.dataGridViewKrakow.Rows.Add(warunki);
}
}
}
class MiastoContainer
{
string miastoName;
public string MiastoName
{
get { return miastoName; }
set { miastoName = value; }
}
string panstwo;
public string Panstwo
{
get { return panstwo; }
set { panstwo = value; }
}
public MiastoContainer()
{ }
public MiastoContainer(string miasto, string panstwo)
{
this.MiastoName = miasto;
this.Panstwo = panstwo;
}
public void Add(MiastoContainer item)
{
((ICollection<MiastoContainer>)this).Add(item);
}
}
}
Your locks are completely useless. As you are locking on an object that you just created, each lock will have it's own identifier and does not affect each other at all.
You need all locks that should exclude each other to use the same object as identifier.
System.Timers.Timer lets you set the SynchronizingObject so that it will invoke the callback on the UI thread. When you create your timers, write:
this.timerKrakow.SynchronizingObject = this;
The timer's elapsed event will then be invoked on the UI thread. That eliminates the need for locks in your event handlers.
You could do the same thing, by the way, with a System.Windows.Forms.Timer, which always invokes the event handler on the UI thread.
The drawback to raising the event on the UI thread is that it might block the user interface. It depends on how much time is spent in the event handler. If your event handler is very quick, then this isn't a problem. If it will take 100 milliseconds to process the event handler, though, you probably don't want to do it on the UI thread.
If you elect not to do it on the UI thread, you need to synchronize access to the UI. The timer event handler can't just modify user interface elements. Instead, you need to call this.Invoke so that any UI modification is done on the UI thread.
I strongly suggest that you NOT use System.Timers.Timer. As the documentation states:
The Timer component catches and
suppresses all exceptions thrown by
event handlers for the Elapsed event.
This behavior is subject to change in
future releases of the .NET Framework.
In other words, if there is a bug in your event handler that throws an exception, you will never know it. I suggest using System.Windows.Forms.Timer or System.Threading.Timer instead.
I don't fully understand your question, but (unless I'm mistaken) the timer callbacks occur in the ThreadPool (or the GUI thread, dependent on usage), so starting them in different threads is pointless.
It seems to me that you are accessing DataGridView directly from another thread. You should not do that. UI controls must always be called from the UI thread. You can use the ISynchronizeInvoke interface to pass the data into correct thread.
this.dataGridViewNowyYork.Invoke(new Action(() => {
this.dataGridViewNowyYork.Rows.Add(warunki);
}), null);

How to wait for a BackgroundWorker to cancel?

Consider a hypothetical method of an object that does stuff for you:
public class DoesStuff
{
BackgroundWorker _worker = new BackgroundWorker();
...
public void CancelDoingStuff()
{
_worker.CancelAsync();
//todo: Figure out a way to wait for BackgroundWorker to be cancelled.
}
}
How can one wait for a BackgroundWorker to be done?
In the past people have tried:
while (_worker.IsBusy)
{
Sleep(100);
}
But this deadlocks, because IsBusy is not cleared until after the RunWorkerCompleted event is handled, and that event can't get handled until the application goes idle. The application won't go idle until the worker is done. (Plus, it's a busy loop - disgusting.)
Others have add suggested kludging it into:
while (_worker.IsBusy)
{
Application.DoEvents();
}
The problem with that is that is Application.DoEvents() causes messages currently in the queue to be processed, which cause re-entrancy problems (.NET isn't re-entrant).
I would hope to use some solution involving Event synchronization objects, where the code waits for an event - that the worker's RunWorkerCompleted event handlers sets. Something like:
Event _workerDoneEvent = new WaitHandle();
public void CancelDoingStuff()
{
_worker.CancelAsync();
_workerDoneEvent.WaitOne();
}
private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
_workerDoneEvent.SetEvent();
}
But I'm back to the deadlock: the event handler can't run until the application goes idle, and the application won't go idle because it's waiting for an Event.
So how can you wait for an BackgroundWorker to finish?
Update
People seem to be confused by this question. They seem to think that I will be using the BackgroundWorker as:
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += MyWork;
worker.RunWorkerAsync();
WaitForWorkerToFinish(worker);
That is not it, that is not what I'm doing, and that is not what is being asked here. If that were the case, there would be no point in using a background worker.
If I understand your requirement right, you could do something like this (code not tested, but shows the general idea):
private BackgroundWorker worker = new BackgroundWorker();
private AutoResetEvent _resetEvent = new AutoResetEvent(false);
public Form1()
{
InitializeComponent();
worker.DoWork += worker_DoWork;
}
public void Cancel()
{
worker.CancelAsync();
_resetEvent.WaitOne(); // will block until _resetEvent.Set() call made
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
while(!e.Cancel)
{
// do something
}
_resetEvent.Set(); // signal that worker is done
}
There is a problem with this response. The UI needs to continue to process messages while you are waiting, otherwise it will not repaint, which will be a problem if your background worker takes a long time to respond to the cancel request.
A second flaw is that _resetEvent.Set() will never be called if the worker thread throws an exception - leaving the main thread waiting indefinitely - however this flaw could easily be fixed with a try/finally block.
One way to do this is to display a modal dialog which has a timer that repeatedly checks if the background worker has finished work (or finished cancelling in your case). Once the background worker has finished, the modal dialog returns control to your application. The user can't interact with the UI until this happens.
Another method (assuming you have a maximum of one modeless window open) is to set ActiveForm.Enabled = false, then loop on Application,DoEvents until the background worker has finished cancelling, after which you can set ActiveForm.Enabled = true again.
Almost all of you are confused by the question, and are not understanding how a worker is used.
Consider a RunWorkerComplete event handler:
private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (!e.Cancelled)
{
rocketOnPad = false;
label1.Text = "Rocket launch complete.";
}
else
{
rocketOnPad = true;
label1.Text = "Rocket launch aborted.";
}
worker = null;
}
And all is good.
Now comes a situation where the caller needs to abort the countdown because they need to execute an emergency self-destruct of the rocket.
private void BlowUpRocket()
{
if (worker != null)
{
worker.CancelAsync();
WaitForWorkerToFinish(worker);
worker = null;
}
StartClaxon();
SelfDestruct();
}
And there is also a situation where we need to open the access gates to the rocket, but not while doing a countdown:
private void OpenAccessGates()
{
if (worker != null)
{
worker.CancelAsync();
WaitForWorkerToFinish(worker);
worker = null;
}
if (!rocketOnPad)
DisengageAllGateLatches();
}
And finally, we need to de-fuel the rocket, but that's not allowed during a countdown:
private void DrainRocket()
{
if (worker != null)
{
worker.CancelAsync();
WaitForWorkerToFinish(worker);
worker = null;
}
if (rocketOnPad)
OpenFuelValves();
}
Without the ability to wait for a worker to cancel, we must move all three methods to the RunWorkerCompletedEvent:
private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (!e.Cancelled)
{
rocketOnPad = false;
label1.Text = "Rocket launch complete.";
}
else
{
rocketOnPad = true;
label1.Text = "Rocket launch aborted.";
}
worker = null;
if (delayedBlowUpRocket)
BlowUpRocket();
else if (delayedOpenAccessGates)
OpenAccessGates();
else if (delayedDrainRocket)
DrainRocket();
}
private void BlowUpRocket()
{
if (worker != null)
{
delayedBlowUpRocket = true;
worker.CancelAsync();
return;
}
StartClaxon();
SelfDestruct();
}
private void OpenAccessGates()
{
if (worker != null)
{
delayedOpenAccessGates = true;
worker.CancelAsync();
return;
}
if (!rocketOnPad)
DisengageAllGateLatches();
}
private void DrainRocket()
{
if (worker != null)
{
delayedDrainRocket = true;
worker.CancelAsync();
return;
}
if (rocketOnPad)
OpenFuelValves();
}
Now I could write my code like that, but I'm just not gonna. I don't care, I'm just not.
You can check into the RunWorkerCompletedEventArgs in the RunWorkerCompletedEventHandler to see what the status was. Success, canceled or an error.
private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
if(e.Cancelled)
{
Console.WriteLine("The worker was cancelled.");
}
}
Update: To see if your worker has called .CancelAsync() by using this:
if (_worker.CancellationPending)
{
Console.WriteLine("Cancellation is pending, no need to call CancelAsync again");
}
You don't wait for the background worker to complete. That pretty much defeats the purpose of launching a separate thread. Instead, you should let your method finish, and move any code that depends on completion to a different place. You let the worker tell you when it's done and call any remaining code then.
If you want to wait for something to complete use a different threading construct that provides a WaitHandle.
Why can't you just tie into the BackgroundWorker.RunWorkerCompleted Event. It's a callback that will "Occur when the background operation has completed, has been canceled, or has raised an exception."
I don't understand why you'd want to wait for a BackgroundWorker to complete; it really seems like the exact opposite of the motivation for the class.
However, you could start every method with a call to worker.IsBusy and have them exit if it is running.
Hm maybe I am not getting your question right.
The backgroundworker calls the WorkerCompleted event once his 'workermethod' (the method/function/sub that handles the backgroundworker.doWork-event) is finished so there is no need for checking if the BW is still running.
If you want to stop your worker check the cancellation pending property inside your 'worker method'.
The workflow of a BackgroundWorker object basically requires you to handle the RunWorkerCompleted event for both normal execution and user cancellation use cases. This is why the property RunWorkerCompletedEventArgs.Cancelled exists. Basically, doing this properly requires that you consider your Cancel method to be an asynchronous method in itself.
Here's an example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
namespace WindowsFormsApplication1
{
public class AsyncForm : Form
{
private Button _startButton;
private Label _statusLabel;
private Button _stopButton;
private MyWorker _worker;
public AsyncForm()
{
var layoutPanel = new TableLayoutPanel();
layoutPanel.Dock = DockStyle.Fill;
layoutPanel.ColumnStyles.Add(new ColumnStyle());
layoutPanel.ColumnStyles.Add(new ColumnStyle());
layoutPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
layoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
_statusLabel = new Label();
_statusLabel.Text = "Idle.";
layoutPanel.Controls.Add(_statusLabel, 0, 0);
_startButton = new Button();
_startButton.Text = "Start";
_startButton.Click += HandleStartButton;
layoutPanel.Controls.Add(_startButton, 0, 1);
_stopButton = new Button();
_stopButton.Enabled = false;
_stopButton.Text = "Stop";
_stopButton.Click += HandleStopButton;
layoutPanel.Controls.Add(_stopButton, 1, 1);
this.Controls.Add(layoutPanel);
}
private void HandleStartButton(object sender, EventArgs e)
{
_stopButton.Enabled = true;
_startButton.Enabled = false;
_worker = new MyWorker() { WorkerSupportsCancellation = true };
_worker.RunWorkerCompleted += HandleWorkerCompleted;
_worker.RunWorkerAsync();
_statusLabel.Text = "Running...";
}
private void HandleStopButton(object sender, EventArgs e)
{
_worker.CancelAsync();
_statusLabel.Text = "Cancelling...";
}
private void HandleWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
_statusLabel.Text = "Cancelled!";
}
else
{
_statusLabel.Text = "Completed.";
}
_stopButton.Enabled = false;
_startButton.Enabled = true;
}
}
public class MyWorker : BackgroundWorker
{
protected override void OnDoWork(DoWorkEventArgs e)
{
base.OnDoWork(e);
for (int i = 0; i < 10; i++)
{
System.Threading.Thread.Sleep(500);
if (this.CancellationPending)
{
e.Cancel = true;
e.Result = false;
return;
}
}
e.Result = true;
}
}
}
If you really really don't want your method to exit, I'd suggest putting a flag like an AutoResetEvent on a derived BackgroundWorker, then override OnRunWorkerCompleted to set the flag. It's still kind of kludgy though; I'd recommend treating the cancel event like an asynchronous method and do whatever it's currently doing in the RunWorkerCompleted handler.
I'm a little late to the party here (about 4 years) but what about setting up an asynchronous thread that can handle a busy loop without locking the UI, then have the callback from that thread be the confirmation that the BackgroundWorker has finished cancelling?
Something like this:
class Test : Form
{
private BackgroundWorker MyWorker = new BackgroundWorker();
public Test() {
MyWorker.DoWork += new DoWorkEventHandler(MyWorker_DoWork);
}
void MyWorker_DoWork(object sender, DoWorkEventArgs e) {
for (int i = 0; i < 100; i++) {
//Do stuff here
System.Threading.Thread.Sleep((new Random()).Next(0, 1000)); //WARN: Artificial latency here
if (MyWorker.CancellationPending) { return; } //Bail out if MyWorker is cancelled
}
}
public void CancelWorker() {
if (MyWorker != null && MyWorker.IsBusy) {
MyWorker.CancelAsync();
System.Threading.ThreadStart WaitThread = new System.Threading.ThreadStart(delegate() {
while (MyWorker.IsBusy) {
System.Threading.Thread.Sleep(100);
}
});
WaitThread.BeginInvoke(a => {
Invoke((MethodInvoker)delegate() { //Invoke your StuffAfterCancellation call back onto the UI thread
StuffAfterCancellation();
});
}, null);
} else {
StuffAfterCancellation();
}
}
private void StuffAfterCancellation() {
//Things to do after MyWorker is cancelled
}
}
In essence what this does is fire off another thread to run in the background that just waits in it's busy loop to see if the MyWorker has completed. Once MyWorker has finished cancelling the thread will exit and we can use it's AsyncCallback to execute whatever method we need to follow the successful cancellation - it'll work like a psuedo-event. Since this is separate from the UI thread it will not lock the UI while we wait for MyWorker to finish cancelling. If your intention really is to lock and wait for the cancel then this is useless to you, but if you just want to wait so you can start another process then this works nicely.
I know this is really late (5 years) but what you are looking for is to use a Thread and a SynchronizationContext. You are going to have to marshal UI calls back to the UI thread "by hand" rather than let the Framework do it auto-magically.
This allows you to use a Thread that you can Wait for if needs be.
Imports System.Net
Imports System.IO
Imports System.Text
Public Class Form1
Dim f As New Windows.Forms.Form
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
BackgroundWorker1.WorkerReportsProgress = True
BackgroundWorker1.RunWorkerAsync()
Dim l As New Label
l.Text = "Please Wait"
f.Controls.Add(l)
l.Dock = DockStyle.Fill
f.StartPosition = FormStartPosition.CenterScreen
f.FormBorderStyle = Windows.Forms.FormBorderStyle.None
While BackgroundWorker1.IsBusy
f.ShowDialog()
End While
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim i As Integer
For i = 1 To 5
Threading.Thread.Sleep(5000)
BackgroundWorker1.ReportProgress((i / 5) * 100)
Next
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Me.Text = e.ProgressPercentage
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
f.Close()
End Sub
End Class
Fredrik Kalseth's solution to this problem is the best I've found so far. Other solutions use Application.DoEvent() that can cause problems or simply don't work. Let me cast his solution into a reusable class. Since BackgroundWorker is not sealed, we can derive our class from it:
public class BackgroundWorkerEx : BackgroundWorker
{
private AutoResetEvent _resetEvent = new AutoResetEvent(false);
private bool _resetting, _started;
private object _lockObject = new object();
public void CancelSync()
{
bool doReset = false;
lock (_lockObject) {
if (_started && !_resetting) {
_resetting = true;
doReset = true;
}
}
if (doReset) {
CancelAsync();
_resetEvent.WaitOne();
lock (_lockObject) {
_started = false;
_resetting = false;
}
}
}
protected override void OnDoWork(DoWorkEventArgs e)
{
lock (_lockObject) {
_resetting = false;
_started = true;
_resetEvent.Reset();
}
try {
base.OnDoWork(e);
} finally {
_resetEvent.Set();
}
}
}
With flags and proper locking, we make sure that _resetEvent.WaitOne() really gets only called if some work has been started, otherwise _resetEvent.Set(); might never been called!
The try-finally ensures that _resetEvent.Set(); will be called, even if an exception should occur in our DoWork-handler. Otherwise the application could freeze forever when calling CancelSync!
We would use it like this:
BackgroundWorkerEx _worker;
void StartWork()
{
StopWork();
_worker = new BackgroundWorkerEx {
WorkerSupportsCancellation = true,
WorkerReportsProgress = true
};
_worker.DoWork += Worker_DoWork;
_worker.ProgressChanged += Worker_ProgressChanged;
}
void StopWork()
{
if (_worker != null) {
_worker.CancelSync(); // Use our new method.
}
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 20; i++) {
if (worker.CancellationPending) {
e.Cancel = true;
break;
} else {
// Simulate a time consuming operation.
System.Threading.Thread.Sleep(500);
worker.ReportProgress(5 * i);
}
}
}
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressLabel.Text = e.ProgressPercentage.ToString() + "%";
}
You can also add a handler to the RunWorkerCompleted event as shown here:
BackgroundWorker Class (Microsoft documentation).
Just wanna say I came here because I need a background worker to wait while I was running an async process while in a loop, my fix was way easier than all this other stuff^^
foreach(DataRow rw in dt.Rows)
{
//loop code
while(!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync();
}
}
Just figured I'd share because this is where I ended up while searching for a solution. Also, this is my first post on stack overflow so if its bad or anything I'd love critics! :)
Closing the form closes my open logfile. My background worker writes that logfile, so I can't let MainWin_FormClosing() finish until my background worker terminates. If I don't wait for my background worker to terminate, exceptions happen.
Why is this so hard?
A simple Thread.Sleep(1500) works, but it delays shutdown (if too long), or causes exceptions (if too short).
To shut down right after the background worker terminates, just use a variable. This is working for me:
private volatile bool bwRunning = false;
...
private void MainWin_FormClosing(Object sender, FormClosingEventArgs e)
{
... // Clean house as-needed.
bwInstance.CancelAsync(); // Flag background worker to stop.
while (bwRunning)
Thread.Sleep(100); // Wait for background worker to stop.
} // (The form really gets closed now.)
...
private void bwBody(object sender, DoWorkEventArgs e)
{
bwRunning = true;
BackgroundWorker bw = sender as BackgroundWorker;
... // Set up (open logfile, etc.)
for (; ; ) // infinite loop
{
...
if (bw.CancellationPending) break;
...
}
... // Tear down (close logfile, etc.)
bwRunning = false;
} // (bwInstance dies now.)
You can piggy back off of the RunWorkerCompleted event. Even if you've already added an event handler for _worker, you can add another an they will execute in the order in which they were added.
public class DoesStuff
{
BackgroundWorker _worker = new BackgroundWorker();
...
public void CancelDoingStuff()
{
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((sender, e) =>
{
// do whatever you want to do when the cancel completes in here!
});
_worker.CancelAsync();
}
}
this could be useful if you have multiple reasons why a cancel may occur, making the logic of a single RunWorkerCompleted handler more complicated than you want. For instance, cancelling when a user tries to close the form:
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (_worker != null)
{
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((sender, e) => this.Close());
_worker.CancelAsync();
e.Cancel = true;
}
}
I use async method and await to wait for the worker finishing its job:
public async Task StopAsync()
{
_worker.CancelAsync();
while (_isBusy)
await Task.Delay(1);
}
and in DoWork method:
public async Task DoWork()
{
_isBusy = true;
while (!_worker.CancellationPending)
{
// Do something.
}
_isBusy = false;
}
You may also encapsulate the while loop in DoWork with try ... catch to set _isBusy is false on exception. Or, simply check _worker.IsBusy in the StopAsync while loop.
Here is an example of full implementation:
class MyBackgroundWorker
{
private BackgroundWorker _worker;
private bool _isBusy;
public void Start()
{
if (_isBusy)
throw new InvalidOperationException("Cannot start as a background worker is already running.");
InitialiseWorker();
_worker.RunWorkerAsync();
}
public async Task StopAsync()
{
if (!_isBusy)
throw new InvalidOperationException("Cannot stop as there is no running background worker.");
_worker.CancelAsync();
while (_isBusy)
await Task.Delay(1);
_worker.Dispose();
}
private void InitialiseWorker()
{
_worker = new BackgroundWorker
{
WorkerSupportsCancellation = true
};
_worker.DoWork += WorkerDoWork;
}
private void WorkerDoWork(object sender, DoWorkEventArgs e)
{
_isBusy = true;
try
{
while (!_worker.CancellationPending)
{
// Do something.
}
}
catch
{
_isBusy = false;
throw;
}
_isBusy = false;
}
}
To stop the worker and wait for it runs to the end:
await myBackgroundWorker.StopAsync();
The problems with this method are:
You have to use async methods all the way.
await Task.Delay is inaccurate. On my PC, Task.Delay(1) actually waits ~20ms.
oh man, some of these have gotten ridiculously complex. all you need to do is check the BackgroundWorker.CancellationPending property inside the DoWork handler. you can check it at any time. once it's pending, set e.Cancel = True and bail from the method.
// method here
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = (sender as BackgroundWorker);
// do stuff
if(bw.CancellationPending)
{
e.Cancel = True;
return;
}
// do other stuff
}

Categories

Resources