I am trying to understand what I can and cant do with background workers. I have seen a fair amount of posts on this but it all seems to involve some operation with loops and you cancel an operation within a loop. I am wanting to find out if I can cancel some operation on a background worker without a loop.
I have the following simple form that I'm playing with:
which contains the following code:
string[,] TestData = new string[300000, 100];
List<string> TestDataList;
private static Random random = new Random();
public Form1()
{
InitializeComponent();
// Loading up some fake data
for (int i = 0; i < 300000; i++)
{
for (int j = 0; j < 100; j++)
{
this.TestData[i, j] = RandomString(10) + j.ToString();
}
}
}
public static string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
which loads a string array with a lot of dummy data. The start button method is as follows:
private void StartWork_Click(object sender, EventArgs e)
{
try
{
System.Threading.SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_Complete);
bw.RunWorkerAsync();
}
catch (Exception ex)
{
MessageBox.Show("Something went wrong.\nError:" + ex.Message);
}
}
And I also have:
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
this.TestDataList = this.TestData.Cast<string>()
.Select((s, i) => new { GroupIndex = i / 100, Item = s.Trim().ToLower() })
.GroupBy(g => g.GroupIndex)
.Select(g => string.Join(",", g.Select(x => x.Item))).ToList();
}
private void bw_Complete(object sender, RunWorkerCompletedEventArgs e)
{
this.showWorkingLabel.Text = "Work done";
}
private void btnCancel_Click(object sender, EventArgs e)
{
// I want to cancel the work with this button
// Then show
this.showWorkingLabel.Text = "Work Cancelled";
}
So you'll notice that my bw_DoWork method does not contain any loops, just a single operation and I want to know if:
If I can kill/cancel the background worker by clicking the Cancel button while the following code is being executed:
.Select((s, i) => new { GroupIndex = i / 100, Item = s.Trim().ToLower() })
.GroupBy(g => g.GroupIndex)
.Select(g => string.Join(",", g.Select(x => x.Item))).ToList();
Can I update the label showWorkingLabel while the background work is happening so that it continuously shows ".", "..", "..." and then back to "." like a progress bar to indicate work is still happening
You need first to support cancellation
bw.WorkerSupportsCancellation = true;
Then you need to share a cancellation token at form level
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken cancellationToken;
Inside your work you need to throw the cancellation:
cancellationToken.ThrowIfCancellationRequested();
Or handle it gracefully with the background worker even for pending cancellations: BackgroundWorker.CancellationPending
And in the cancell button you can call the cancellation like this:
cts.Cancel();
Using your code it will become something similar to the following indication, you should handle graceful cancellations:
string[,] TestData = new string[30000, 100];
List<string> TestDataList;
private static Random random = new Random();
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken cancellationToken;
private void BtnStart_Click(object sender, EventArgs e)
{
try
{
this.showWorkingLabel.Text = "Work start";
System.Threading.SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
cancellationToken = cts.Token;
cancellationToken.Register(bw.CancelAsync);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_Complete);
bw.RunWorkerAsync();
}
catch (Exception ex)
{
MessageBox.Show("Something went wrong.\nError:" + ex.Message);
}
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
cancellationToken.ThrowIfCancellationRequested();
this.TestDataList = this.TestData
.Cast<string>()
.Select((s, i) => new { GroupIndex = i / 100, Item = s.Trim().ToLower() })
.GroupBy(g => g.GroupIndex)
.Select(g =>
{
cancellationToken.ThrowIfCancellationRequested();
return string.Join(",", g.Select(x => x.Item));
})
.ToList();
}
private void btnCancel_Click(object sender, EventArgs e)
{
cts.Cancel();
this.showWorkingLabel.Text = "Work Cancelled";
}
As per the MSDN page for BackgroundWorker:
When creating the worker, you can make it support cancellation by setting
backgroundWorker.WorkerSupportsCancellation = true;
You can request cancellation by calling CancelAsync() on the BackgroundWorker.
Then your BackgroundWorker should periodically check the BackgroundWorker.CancellationPending property, and if set to true, it should cancel its operation. As Sir Rufo pointed out in a comment, don't forget to inside the DoWork delegate you have to set DoWorkEventArgs.Cancel to true.
The MSDN page I linked has additional examples of usage in real code.
Here is a working example using the BackgroundWorker builtin cancellation support.
// We need to remember the BackgroundWorker
private BackgroundWorker bw;
private void StartWork_Click( object sender, EventArgs e )
{
bw = new BackgroundWorker
{
WorkerSupportsCancellation = true,
};
bw.DoWork += Bw_DoWork;
bw.RunWorkerCompleted += Bw_RunWorkerCompleted;
bw.RunWorkerAsync();
showWorkingLabel.Text = "Work started ...";
}
private void Bw_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
{
if ( e.Cancelled ) // was it cancelled?
{
showWorkingLabel.Text = "Work cancelled.";
return;
}
if ( e.Error != null ) // any error?
{
showWorkingLabel.Text = "Work faulted - " + e.Error.Message;
return;
}
// assign the bw Result to the field
this.TestDataList = (List<string>)e.Result;
showWorkingLabel.Text = "Work completed.";
}
private void Bw_DoWork( object sender, DoWorkEventArgs e )
{
try
{
e.Result = this.TestData
.Cast<string>()
.Select( ( s, i ) =>
{
// check for cancellation
if ( bw.CancellationPending )
throw new OperationCanceledException();
return new
{
GroupIndex = i / 100,
Item = s.Trim().ToLower()
};
} )
.GroupBy( g => g.GroupIndex )
.Select( g =>
{
// check for cancellation
if ( bw.CancellationPending )
throw new OperationCanceledException();
return string.Join( ",", g.Select( x => x.Item ) );
} )
.ToList();
}
catch ( OperationCanceledException )
{
e.Cancel = true;
}
}
private void btnCancel_Click( object sender, EventArgs e )
{
// request cancellation
bw.CancelAsync();
showWorkingLabel.Text = "Work cancellation requested ...";
}
and another doing exactly the same with the modern async/await Task and CancellationToken
private CancellationTokenSource cts;
private async void StartWork_Click( object sender, EventArgs e )
{
showWorkingLabel.Text = "Work started ...";
cts = new CancellationTokenSource();
var token = cts.Token;
try
{
TestDataList = await Task.Run( () =>
{
return this.TestData
.Cast<string>()
.Select( ( s, i ) =>
{
token.ThrowIfCancellationRequested();
return new
{
GroupIndex = i / 100,
Item = s.Trim().ToLower()
};
} )
.GroupBy( g => g.GroupIndex )
.Select( g =>
{
token.ThrowIfCancellationRequested();
return string.Join( ",", g.Select( x => x.Item ) );
} )
.ToList();
}, token );
showWorkingLabel.Text = "Work completed.";
}
catch ( OperationCanceledException )
{
showWorkingLabel.Text = "Work canceled.";
}
catch ( Exception ex )
{
showWorkingLabel.Text = "Work faulted - " + ex.Message;
}
}
private void btnCancel_Click( object sender, EventArgs e )
{
cts.Cancel();
showWorkingLabel.Text = "Work cancellation requested ...";
}
Related
Hello I am trying to populate a progress bar but the ReportProgress call its not been executed for some reason.
Here is my code
//create status_Worker
status_Worker = new BackgroundWorker();
status_Worker.DoWork += new DoWorkEventHandler(Status_DoWork);
status_Worker.ProgressChanged += new ProgressChangedEventHandler(Worker_ProgressChanged);
status_Worker.WorkerReportsProgress = true;
status_Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Worker_RunWorkerCompleted);
private void Status_DoWork(object sender, DoWorkEventArgs e)
{
//make call to Logger class getStatus method
_logger.getStatus(sender);
}
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressbar1.Value = e.ProgressPercentage;
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
return;
}
else
{
Start_button.IsEnabled = true;
}
}
private void Start_button_Click(object sender, RoutedEventArgs e)
{
//initiate status_Worker when button is clicked
status_Worker.RunWorkerAsync();
Start_button.IsEnabled = false;
}
Now inside the Logger class I have the getStatus() method. i make a call to a local server to get status of the files been processed and all that works and I see the values been updated automatically on my MainWindow.Status.
public async Task getStatus(object sender)
{
BackgroundWorker statusWorker = (BackgroundWorker)sender;
//Making a call to ReportProgress here works and it shows the progress bar
//statusWorker.ReportProgress(99);
//REQUEST STATUS from a server
//Status format
//CurrentParser, NumberOfFilesToParse,CountOfCompletedFiles,Status, NumberOfProcessRunning
int CountOfCompletedFiles;
int NumberOfFilesToParse;
int percent;
string status = "Running";
string[] stats;
char[] delimiterChars = { ' ', ',', '.', ':', '\t' };
while(status!="Complete")
{
var getstatus = await request.GetStringAsync("http://localhost:8085/status");
logs.Add(getstatus);
stats = getstatus.Split(delimiterChars);
NumberOfFilesToParse = Int32.Parse(stats[1]);
CountOfCompletedFiles = Int32.Parse(stats[2]);
status = stats[3];
Thread.Sleep(1000);
MainWindow.main.Status = "Files to process: " + NumberOfFilesToParse + " Files completed: " + CountOfCompletedFiles + " Status: " + status;
if(NumberOfFilesToParse!=0 && status!="Complete")
{
percent = (CountOfCompletedFiles * 100) / NumberOfFilesToParse;
//a call to ReportProgress here stalls the program at this point
//statusWorker.ReportProgress(percent);
}
}
MainWindow.main.Status = "Completed!";
}
A call to ReportProgress at the start of the getStatus method works but a call to ReportProgress during or after my while loop results in process stalling at that point. Even when using static numbers ReportProgress(99) it only executes at the beginning
Your Status_DoWork method is doing fire-and-forget. It's calling an async Task method and then ignoring the Task it returns.
One of the problems you've run into is that BackgroundWorker simply doesn't work with async. What's actually happening is that as soon as the first await is reached in getStatus, it returns an incomplete Task to Status_DoWork, which then exits. This causes the BackgroundWorker to finish, so raising progress events no longer makes sense for that BackgroundWorker.
The modern replacement for BackgroundWorker is Task.Run, which includes support for progress reporting. Ideally, you would only use Task.Run for CPU-bound methods, not the I/O-bound methods:
private void Start_button_Click(object sender, RoutedEventArgs e)
{
Start_button.IsEnabled = false;
var progress = new Progress<int>(update => progressbar1.Value = update);
try
{
await _logger.getStatus(progress);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
Start_button.IsEnabled = true;
}
}
public async Task getStatus(IProgress<int> progress)
{
int CountOfCompletedFiles;
int NumberOfFilesToParse;
int percent;
string status = "Running";
string[] stats;
char[] delimiterChars = { ' ', ',', '.', ':', '\t' };
while(status!="Complete")
{
var getstatus = await request.GetStringAsync("http://localhost:8085/status");
logs.Add(getstatus);
stats = getstatus.Split(delimiterChars);
NumberOfFilesToParse = Int32.Parse(stats[1]);
CountOfCompletedFiles = Int32.Parse(stats[2]);
status = stats[3];
await Task.Run(() => Thread.Sleep(1000)); // process file in Task.Run
MainWindow.main.Status = "Files to process: " + NumberOfFilesToParse + " Files completed: " + CountOfCompletedFiles + " Status: " + status;
if(NumberOfFilesToParse!=0 && status!="Complete")
{
percent = (CountOfCompletedFiles * 100) / NumberOfFilesToParse;
progress.Report(percent);
}
}
MainWindow.main.Status = "Completed!";
}
I have a button that starts two threads
private void CrawdBtn_Click(object sender, EventArgs e)
{
CrawdBtn.Enabled = false;
t = new Thread(AddLinksToList);
b = new Thread(EnqueueFromList);
t.Start();
b.Start();
}
and there are another buttons to pause, Resume, Stop those threads
My question is how can I disable (pause, Resume, Stop) buttons while the threads are working and re enable Crawl after the threads finished
Here is how you could start a Thread and have a way to await its completion:
public static Thread CreateAwaitableThread(Action action, out Task threadCompletion)
{
var tcs = new TaskCompletionSource<bool>();
threadCompletion = tcs.Task;
return new Thread(() =>
{
try
{
action();
}
finally
{
tcs.SetResult(true);
}
});
}
This method returns the newly created Thread, and also a Task that will be completed when the Thread is completed. You could use it like this:
private async void CrawdBtn_Click(object sender, EventArgs e)
{
CrawdBtn.Enabled = false;
Thread t1 = CreateAwaitableThread(AddLinksToList, out var t1c);
Thread t2 = CreateAwaitableThread(EnqueueFromList, out var t2c);
t1.Start();
t2.Start();
await Task.WhenAll(t1c, t2c);
CrawdBtn.Enabled = true;
}
In case of an exception the error will not be propagated through the Task. It is assumed that the delegates already include error handling logic. If not, an unhandled exception will occur as usual.
To solve your problem you can make a thread to check the ThreadState of thread t and thread b
private void btnstart_Click(object sender, EventArgs e)
{
t = new Thread(AddLinksToList);
b = new Thread(EnqueueFromList);
t.Start();
b.Start();
if (threadchecker == null)//this if determines Whether it's the first time or not
{
threadchecker = new Thread(() => ChekingStateOfThreads());
threadchecker.IsBackground = true;
threadchecker.Start();
}
}
Since the thread wants to check the ThreadState of those threads it should always run.
and This is ChekingStateOfThreads
private void ChekingStateOfThreads()
{
while (true)
{
Thread.Sleep(1000);
if (t.ThreadState == ThreadState.Stopped)
{
this.Invoke(new Action(() =>
{
btnpause.Enabled = btnstart.Enabled = false;
btnresume.Enabled = btnstop.Enabled = true;
}));
}
else if (t.ThreadState == ThreadState.Running)
{
this.Invoke(new Action(() =>
{
btnstart.Enabled = btnresume.Enabled = false;
btnpause.Enabled = btnstop.Enabled = true;
}));
}
else if (t.ThreadState == ThreadState.Aborted)
{
this.Invoke(new Action(() =>
{
btnstart.Enabled = true;
btnpause.Enabled = btnresume.Enabled = btnstop.Enabled = false;
}));
}
else if (t.ThreadState == ThreadState.Suspended)
{
this.Invoke(new Action(() =>
{
btnpause.Enabled = btnstart.Enabled = false;
btnresume.Enabled = btnstop.Enabled = true;
}));
}
}
}
The concept of function is pretty simple. Each 1 second the thread is check the state of thread t.
See Why use Invoke on Controls in .net? to figure out why should we use INVOKE.
To abort the threadchecker just use the Form_closing Event
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
threadchecker.Abort();
}
I get confused, when tried to use async task. Everything is fine if I use this method without parameters, but when I add parameters to it, it says that cannot convert System.Threading.Tasks.Task to System.Func. So how to use it properly and fix this error?
Main:
Timers t = new Timers();
public Form1()
{
InitializeComponent();
t.StartTimerTemplate(TEMPLATEupdateSong("test", "test", "test"));//error occurs in this line
}
async Task TEMPLATEupdateSong(string url, string sender, string labelName)
{
string nowPlaying = await getter.getString(url);
nowPlaying = XDocument.Parse(nowPlaying).ToString();
var outputLabel = this.Controls.OfType<Label>()
.FirstOrDefault(control => control.Name == labelName);
outputLabel.Invoke(new Action(() =>
outputLabel.Text = sender + "\n" + nowPlaying
));
}
Timers.cs:
public void StartTimerTemplate(Func<Task> updateTemplate)
{
System.Timers.Timer timer = new System.Timers.Timer(50000);
timer.AutoReset = true;
timer.Elapsed += (sender, e) => timer_Elapsed(sender, e, updateTemplate());
timer.Start();
}
async void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e, Task task)
{
await task;
}
Your method expects a Func<Task>, not a Task.
t.StartTimerTemplate(() => TEMPLATEupdateSong("test", "test", "test"));
Or
t.StartTimerTemplate(async () => await TEMPLATEupdateSong("test", "test", "test"));
I am trying to download minecraft files libraries and assets using listbox to select version then download by click on the button but when I click on the button I get this error
"System.FormatException: 'Input string was not in a correct format.'"
on await MCDownload(files, ctoken.Token);
please reply what should I do.
Thanks!
private async void LaunchMinecraft1_Click(object sender, RoutedEventArgs e)
{
if (Directory.Exists(jsonloc + #"\versions\" + versionsList.SelectedItem.ToString()))
{
MessageBox.Show("Version already exists");
return;
}
ctoken = new CancellationTokenSource();
var files = await GetFiles(versionsList.SelectedItem.ToString(), totalPercentage.Content);
await MCDownload(files, ctoken.Token);
}
public List<string[]> urls = new List<string[]>();
public async Task MCDownload(List<string[]>urls, CancellationToken _ctsblock)
{
totalMB = 0;
_cts = new CancellationTokenSource();
sw.Start();
int count = urls.Count;
if (urls != null && urls.Count != 0)
{
System.Timers.Timer myTimer = new System.Timers.Timer();
myTimer.Elapsed += (s, e) =>
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
progressBar.Visibility = Visibility.Visible;
downloadedMB.Content = string.Format("{0} MB", ((totalMB / 1024f) / 1024f).ToString("0.00"));
}));
};
myTimer.Interval = 100; // 1000 ms is one second
myTimer.Start();
ServicePointManager.DefaultConnectionLimit = Int32.Parse(Settings.Default["download_threads"].ToString());
var block = new ActionBlock<string[]>(async url =>
{
try
{
await DownloadLibraries(url);
}
catch (WebException) { }
catch (OperationCanceledException) { }
catch (Exception e)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
MessageBox.Show("There may have been some errors while downloading the game. It shouldn't be a problem though. If you experience any issue, try to reinstall again or contact us!");
}));
Console.WriteLine("ERRORE IN" + url[3] + "\r\n" + e);
}
}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Int32.Parse(Properties.Settings.Default["download_threads"].ToString()), CancellationToken = _ctsblock });
foreach (var url in urls)
{
block.Post(url);
}
block.Complete();
try
{
await block.Completion;
}
catch (TaskCanceledException)
{
return;
}
myTimer.Stop();
Application.Current.Dispatcher.Invoke(new Action(() =>
{
downloadedMB.Content = null;
}));
progressBar.Value = 100;
progressBar.Visibility = Visibility.Hidden;
return;
}
}
Consider a form with 2 buttons and a richtextbox:
public partial class MainForm : Form
{
CancellationTokenSource cts;
CancellationToken token;
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
cts.Dispose();
}
private void btnStart_Click(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
}
private void btnCancel_Click(object sender, EventArgs e)
{
try
{
cts.Cancel();
cts.Dispose();
}
catch (ObjectDisposedException exc)
{
MessageBox.Show(exc.GetType().Name);
//object disposed
}
}
public void WriteSomeLines()
{
if (ControlInvokeRequired(rtbLoops, () => rtbLoops.Text += "Starting new loop \r\n")) ;
else rtbLoops.Text += "Starting new loop \r\n";
for (int i = 0; i < 30; i++)
{
try
{
if (ControlInvokeRequired(rtbLoops, () => { rtbLoops.AppendText("New line " + i + "\r\n"); rtbLoops.ScrollToCaret(); })) ;
else rtbLoops.AppendText("New line " + i + "\r\n");
Thread.Sleep(250);
token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException ae)
{
MessageBox.Show(ae.GetType().Name);
return;
}
}
return;
}
public bool ControlInvokeRequired(Control c, Action a)
{
if (c.InvokeRequired)
c.Invoke(new MethodInvoker(delegate { a(); }));
else
return false;
return true;
}
}
Is there a difference if WriteSomeLines() is returning void and I use return inside, or if WriteSomeLines() returns Task and I do return null there? I read that I cannot use await with void returning methods, but inserting
await task;
after task declaration (in the code above) compiles perfectly fine, and runs with no issues.
Edit:
private async void btnStart_Click(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
await task;
rtbLoops.Text += "Task complete";
}
This compiles with no issues if WriteSomeLines() returns void.
Also, slightly unrealted, am I disposing CancellationTokenSource correctly here?
Second Edit:
So is this the correct approach:
private async void btnStart_Click(object sender, EventArgs e)
{
cts.Dispose();
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
bool result = await task;
if(result == true) rtbLoops.Text += "Task complete \r\n";
}
and
public async Task<bool> WriteSomeLines()
{
if (ControlInvokeRequired(rtbLoops, () => rtbLoops.Text += "Starting new loop \r\n")) ;
else rtbLoops.Text += "Starting new loop \r\n";
for (int i = 0; i < 30; i++)
{
try
{
if (ControlInvokeRequired(rtbLoops, () => { rtbLoops.AppendText("New line " + i + "\r\n"); rtbLoops.ScrollToCaret(); })) ;
else rtbLoops.AppendText("New line " + i + "\r\n");
await Task.Delay(250);
token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException ae)
{
MessageBox.Show(ae.GetType().Name);
return false;
}
}
return true;
You should never return a null task; that should cause a runtime NullReferenceException error.
You can use await within an async void method, but you cannot use await to consume an async void method (because you cannot await void).
I recommend that you review my async intro blog post; it should help you get a better understanding of async and await.
am I disposing CancellationTokenSource correctly here?
Your start button needs to cancel/dispose the old cts when it creates a new one.