How to reset a CancellationToken properly? - c#

I have been playing round with the Async CTP this morning and have a simple program with a button and a label. Click the button and it starts updating the label, stop the button it stops writing to the label. However, I'm not sure how to reset the CancellationTokenSource so that I can restart the process.
My code:
public partial class MainWindow : Window
{
CancellationTokenSource cts = new CancellationTokenSource();
public MainWindow()
{
InitializeComponent();
button.Content = "Start";
}
async Task DoWork(CancellationToken cancelToken)
{
int i = 0;
while (!cancelToken.IsCancellationRequested)
{
label.Content = i++.ToString();
await TaskEx.Delay(50, cancelToken);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (button.Content == "Start")
{
button.Content = "Stop";
DoWork(cts.Token);
}
else
{
button.Content = "Start";
cts.Cancel();
}
}
}

You need to recreate the CancellationTokenSource - there is no way to "reset" this once you set it.
This could be as simple as:
private void Button_Click(object sender, RoutedEventArgs e)
{
if (button.Content == "Start")
{
button.Content = "Stop";
cts.Dispose(); // Clean up old token source....
cts = new CancellationTokenSource(); // "Reset" the cancellation token source...
DoWork(cts.Token);
}
else
{
button.Content = "Start";
cts.Cancel();
}
}

I had the same problem and I figured it out that the best way to solve it is to create cancellation token source newly just before calling the method.
this is what I do on my start button click:
cancellationTokenSource = new CancellationTokenSource();
cancellationToken = cancellationTokenSource.Token;
Task.Factory.StartNew(StartUpload, cancellationToken);
I change the caption for the same button to cancel and when a click occurs on cancel, I call
cancellationTokenSource.Cancel();
Here is the full code:
if (button3.Text != "&Start Upload")
{
cancellationTokenSource.Cancel();
}
else
{
try
{
cancellationTokenSource = new CancellationTokenSource();
cancellationToken = cancellationTokenSource.Token;
Task.Factory.StartNew(StartUpload, cancellationToken);
}
catch (AggregateException ex)
{
var builder = new StringBuilder();
foreach (var v in ex.InnerExceptions)
builder.Append("\r\n" + v.InnerException);
MessageBox.Show("There was an exception:\r\n" + builder.ToString());
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

Now, with .NET 6 you can use TryReset() method which Attempts to reset the CancellationTokenSource to be used for an unrelated operation. see this issue and CancellationTokenSource.cs for more details.

Related

Background Worker RunWorkerCompleted isn't being called after DoWork finished

I'm creating a simple program that pings all the servers on our network and returns whether the ping requests were successful.
I'm trying to utilise background workers so that the user can press the ping button and the pings run in the background while they can do other things on the UI
DoWork runs fine, there's no loop to keep it there infinitely, and it reaches the line:
r = pinger.Send(s)
and then from my understanding it ends and so the RunWorkCompleted method should be called?
I'm relearning programming after a long abscense so if I missed something obvious I apologise
...
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
}
private void Ping_Btn_Click(object sender, EventArgs e)
{
count = Convert.ToInt32(pingSeconds_TxtBox.Text);
if (backgroundWorker1.IsBusy != true)
{
// Start operation
backgroundWorker1.RunWorkerAsync();
}
}
private static void OnTimedEvent(Object source, ElapsedEventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if(worker.CancellationPending == true)
{
e.Cancel = true;
}
else
{
for(int i = 0; i <= count; i++)
{
MessageBox.Show("something is happening");
// Create ping object
Ping pinger = new Ping();
PingReply r;
// IP to test ping
string s = "###";
try
{
r = pinger.Send(s);
}
catch (Exception b)
{
MessageBox.Show(b.ToString());
}
}
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Show me something");
if(e.Cancelled == true)
{
statusLbl1.Text = "Cancelled";
} else if(e.Error != null)
{
statusLbl1.Text = "Error: " + e.Error.Message;
} else
{
statusLbl1.Text = "YEEEEEEEET";
}
}
...
You need to attach your backgroundWorker1_RunWorkerCompleted event handler to the RunWorkerCompleted event. The C# compiler doesn't hook handlers to events based on naming conventions. You have to do it explicitly.
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
}
I strongly suggest you convert this code to use async await which is much better at representing the flow of code control, rather than using the old BackgroundWorker which is basically deprecated.
Note the following:
The main event handler should be async void but all other async functions should be async Task.
Use of SemaphoreSlim.WaitAsync(0) to check if we are busy.
Ping object needs a using or finally to dispose it, as does the CancellationTokenSource.
<= count looks like it should be < count because you begin at 0.
SemaphoreSlim sem = new SemaphoreSlim(1, 1);
CancellationToken token;
private async void Ping_Btn_Click(object sender, EventArgs e)
{
if (!await sem.WaitAsync(0))
return;
var tokenSource = new CancellationTokenSource();
try
{
var count = Convert.ToInt32(pingSeconds_TxtBox.Text);
await RunPingsAsync(count, tokenSource.Token);
statusLbl1.Text = "YEEEEEEEET";
}
catch (OperationCanceledException)
{
statusLbl1.Text = "Cancelled";
}
catch (Exception e)
{
statusLbl1.Text = "Error: " + e.Error.Message;
}
finally
{
sem.Release();
tokenSource.Dispose();
}
MessageBox.Show("Show me something");
}
private Task RunPingsAsync(int count, CancellationToken token)
{
for(int i = 0; i < count; i++)
{
token.ThrowIfCancellationRequested();
MessageBox.Show("something is happening");
// IP to test ping
string s = "###";
// Create ping object
using (Ping pinger = new Ping())
{
var r = await pinger.SendPingAsync(s);
}
}
}
If you want to keep an infinite loop, then you have to make a loop in your backgroundWorker1_DoWork Method. Something like this
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker) sender;
while (!worker.CancellationPending)
{
//Do your stuff here
for(int i = 0; i <= count; i++)
{
MessageBox.Show("something is happening");
// Create ping object
Ping pinger = new Ping();
PingReply r;
// IP to test ping
string s = "###";
try
{
r = pinger.Send(s);
}
catch (Exception b)
{
MessageBox.Show(b.ToString());
}
}
}
}
Also, it is not a good idea to display message boxes from your background thread, Log it in console or any file.

C# MessageBox in a loop [duplicate]

I have been attempting to have a re-usable modal progress window (I.e. progressForm.ShowDialog()) to show progress from a running async task, including enabling cancellation.
I have seen some implementations that launch start the async task by hooking the Activated event handler on the form, but I need to start the task first, then show the modal dialog that will show it's progress, and then have the modal dialog close when completed or cancellation is completed (note - I want the form closed when cancellation is completed - signalled to close from the task continuation).
I currently have the following - and although this working - are there issues with this - or could this be done in a better way?
I did read that I need to run this CTRL-F5, without debugging (to avoid the AggregateException stopping the debugger in the continuation - and let it be caught in the try catch as in production code)
ProgressForm.cs
- Form with ProgressBar (progressBar1) and Button (btnCancel)
public partial class ProgressForm : Form
{
public ProgressForm()
{
InitializeComponent();
}
public event Action Cancelled;
private void btnCancel_Click(object sender, EventArgs e)
{
if (Cancelled != null) Cancelled();
}
public void UpdateProgress(int progressInfo)
{
this.progressBar1.Value = progressInfo;
}
}
Services.cs
- Class file containing logic consumed by WinForms app (as well as console app)
public class MyService
{
public async Task<bool> DoSomethingWithResult(
int arg, CancellationToken token, IProgress<int> progress)
{
// Note: arg value would normally be an
// object with meaningful input args (Request)
// un-quote this to test exception occuring.
//throw new Exception("Something bad happened.");
// Procressing would normally be several Async calls, such as ...
// reading a file (e.g. await ReadAsync)
// Then processing it (CPU instensive, await Task.Run),
// and then updating a database (await UpdateAsync)
// Just using Delay here to provide sample,
// using arg as delay, doing that 100 times.
for (int i = 0; i < 100; i++)
{
token.ThrowIfCancellationRequested();
await Task.Delay(arg);
progress.Report(i + 1);
}
// return value would be an object with meaningful results (Response)
return true;
}
}
MainForm.cs
- Form with Button (btnDo).
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private async void btnDo_Click(object sender, EventArgs e)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
// Create the ProgressForm, and hook up the cancellation to it.
ProgressForm progressForm = new ProgressForm();
progressForm.Cancelled += () => cts.Cancel();
// Create the progress reporter - and have it update
// the form directly (if form is valid (not disposed))
Action<int> progressHandlerAction = (progressInfo) =>
{
if (!progressForm.IsDisposed) // don't attempt to use disposed form
progressForm.UpdateProgress(progressInfo);
};
Progress<int> progress = new Progress<int>(progressHandlerAction);
// start the task, and continue back on UI thread to close ProgressForm
Task<bool> responseTask
= MyService.DoSomethingWithResultAsync(100, token, progress)
.ContinueWith(p =>
{
if (!progressForm.IsDisposed) // don't attempt to close disposed form
progressForm.Close();
return p.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
Debug.WriteLine("Before ShowDialog");
// only show progressForm if
if (!progressForm.IsDisposed) // don't attempt to use disposed form
progressForm.ShowDialog();
Debug.WriteLine("After ShowDialog");
bool response = false;
// await for the task to complete, get the response,
// and check for cancellation and exceptions
try
{
response = await responseTask;
MessageBox.Show("Result = " + response.ToString());
}
catch (AggregateException ae)
{
if (ae.InnerException is OperationCanceledException)
Debug.WriteLine("Cancelled");
else
{
StringBuilder sb = new StringBuilder();
foreach (var ie in ae.InnerExceptions)
{
sb.AppendLine(ie.Message);
}
MessageBox.Show(sb.ToString());
}
}
finally
{
// Do I need to double check the form is closed?
if (!progressForm.IsDisposed)
progressForm.Close();
}
}
}
Modified code - using TaskCompletionSource as recommended...
private async void btnDo_Click(object sender, EventArgs e)
{
bool? response = null;
string errorMessage = null;
using (CancellationTokenSource cts = new CancellationTokenSource())
{
using (ProgressForm2 progressForm = new ProgressForm2())
{
progressForm.Cancelled +=
() => cts.Cancel();
var dialogReadyTcs = new TaskCompletionSource<object>();
progressForm.Shown +=
(sX, eX) => dialogReadyTcs.TrySetResult(null);
var dialogTask = Task.Factory.StartNew(
() =>progressForm.ShowDialog(this),
cts.Token,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
await dialogReadyTcs.Task;
Progress<int> progress = new Progress<int>(
(progressInfo) => progressForm.UpdateProgress(progressInfo));
try
{
response = await MyService.DoSomethingWithResultAsync(50, cts.Token, progress);
}
catch (OperationCanceledException) { } // Cancelled
catch (Exception ex)
{
errorMessage = ex.Message;
}
finally
{
progressForm.Close();
}
await dialogTask;
}
}
if (response != null) // Success - have valid response
MessageBox.Show("MainForm: Result = " + response.ToString());
else // Faulted
if (errorMessage != null) MessageBox.Show(errorMessage);
}
I think the biggest issue I have, is that using await (instead of
ContinueWith) means I can't use ShowDialog because both are blocking
calls. If I call ShowDialog first the code is blocked at that point,
and the progress form needs to actually start the async method (which
is what I want to avoid). If I call await
MyService.DoSomethingWithResultAsync first, then this blocks and I
can't then show my progress form.
The ShowDialog is indeed a blocking API in the sense it doesn't return until the dialog has been closed. But it is non-blocking in the sense it continues to pump messages, albeit on a new nested message loop. We can utilize this behavior with async/await and TaskCompletionSource:
private async void btnDo_Click(object sender, EventArgs e)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
// Create the ProgressForm, and hook up the cancellation to it.
ProgressForm progressForm = new ProgressForm();
progressForm.Cancelled += () => cts.Cancel();
var dialogReadyTcs = new TaskCompletionSource<object>();
progressForm.Load += (sX, eX) => dialogReadyTcs.TrySetResult(true);
// show the dialog asynchronousy
var dialogTask = Task.Factory.StartNew(
() => progressForm.ShowDialog(),
token,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
// await to make sure the dialog is ready
await dialogReadyTcs.Task;
// continue on a new nested message loop,
// which has been started by progressForm.ShowDialog()
// Create the progress reporter - and have it update
// the form directly (if form is valid (not disposed))
Action<int> progressHandlerAction = (progressInfo) =>
{
if (!progressForm.IsDisposed) // don't attempt to use disposed form
progressForm.UpdateProgress(progressInfo);
};
Progress<int> progress = new Progress<int>(progressHandlerAction);
try
{
// await the worker task
var taskResult = await MyService.DoSomethingWithResultAsync(100, token, progress);
}
catch (Exception ex)
{
while (ex is AggregateException)
ex = ex.InnerException;
if (!(ex is OperationCanceledException))
MessageBox.Show(ex.Message); // report the error
}
if (!progressForm.IsDisposed && progressForm.Visible)
progressForm.Close();
// this make sure showDialog returns and the nested message loop is over
await dialogTask;
}

UI hangs when multiple httpclient or httpresponse sent

trying to get page source from list of urls. but it hangs the UI. i would prefer httpclient so i can use single instance. please let me know what can i do so it wont hang the ui during the process.
async Task AccessTheWebAsync(CancellationToken ct)
{
HttpClient client = new HttpClient();
List<string> urlToSearch = UrlToSearchList();
IEnumerable<Task<string>> downloadTasksQuery =from url in urlToSearch select ProcessURL2(url, client, ct);
List<Task<string>> downloadTasks = downloadTasksQuery.ToList();
while (downloadTasks.Count > 0)
{
Task<string> firstFinishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(firstFinishedTask);
string length = await firstFinishedTask;
resultsTextBox.Text += $"\r\nLength of the download: {length}";
}
}
private List<string> UrlToSearchList()
{
var urlToSearchList = new List<string>();
foreach (var item in lbURLToSearch.Items)
{
urlToSearchList.Add(item.ToString());
}
return urlToSearchList;
}
async Task<string> ProcessURL2(string url, HttpClient client, CancellationToken ct)
{
HttpResponseMessage response = await client.GetAsync(url, ct);
var contents = await response.Content.ReadAsStringAsync();
return contents;
}
private void CancelButton_Click(object sender, EventArgs e)
{
if (cts != null)
{
cts.Cancel();
}
}
private async void StartButton_Click(object sender, EventArgs e)
{
resultsTextBox.Clear();
cts = new CancellationTokenSource();
try
{
await AccessTheWebAsync(cts.Token);
resultsTextBox.Text += "\r\nDownloads complete.";
}
catch (OperationCanceledException)
{
resultsTextBox.Text += "\r\nDownloads canceled.\r\n";
}
catch (Exception)
{
resultsTextBox.Text += "\r\nDownloads failed.\r\n";
}
cts = null;
}
i also tried various async methods. still same result, it hangs. below is one of the async method i tried.
private async void TsbtnStartURLScraper_Click(object sender, EventArgs e)
{
for (int i = 0; i < lbURLToSearch.Items.Count; i++)
{
txtScrapeURL.Text = lbURLToSearch.Items[i].ToString();
string urlAddress = lbURLToSearch.Items[i].ToString();
var returnedresult = await DownloadStringV1(urlAddress);
resultsTextBox.Text += returnedresult;
}
}
public async Task<String> DownloadStringV1(String url)
{
// good code
var request = await reusableHTTPClient.GetAsync(url);
var download = await request.Content.ReadAsStringAsync();
return download;
}
NOTE: what i am trying to do is, loop through a list of url and get page source from them. but when i am doing this it is freezing the form.

Run method Safe cancellation?

I want to call a function in a different thread than my GUI.
I used the below code to trigger the function:
private void button1_Click(object sender, EventArgs e)
{
tokensource = new CancellationTokenSource();
var token = tokensource.Token;
Task.Run(()=>foo() , token);
}
private void foo()
{
// Uses some resources
}
private void button2_Click(object sender, EventArgs e)
{
tokenSource.Cancel();
}
How can I safely close the occupied resources in foo() when the task is cancelled?
You need to pass the token to the function as well. The cancellation token passed to Task.Run won't abort an already running task, it will prevent a scheduled task from running.
Inside foo, you can check the token for cancellation and return, or throw an exception. You can use a using block to dispose resources safely. Eg:
private void foo(CancellationToken token)
{
using(var reader=new StreamReader(somePath)
{
string line;
// Read the line if no cancellation was requested
while (!token.IsCancellationRequested && (line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
This code reads a line only if cancellation wasn't requested and returns quietly otherwise
You can also throw an OperationCancelledException by calling CancellationToken.ThrowIfCancellationRequested
private void foo(CancellationToken token)
{
using(var reader=new StreamReader(somePath)
{
string line;
// Read the line if no cancellation was requested
while ((line = sr.ReadLine()) != null)
{
token.ThrowIfCancellationRequested();
Console.WriteLine(line);
}
}
}
This will throw an exception that will be raised in the calling code when the task's result is retrieved, eg when using await Task.Run(..) or Task.Run(..).Wait()
Your Method should handle the CancellationToken like that:
public static void Main(string[] args)
{
var tokenSource = new CancellationTokenSource();
Console.WriteLine("Press CTRL+C to cancel important work");
Console.CancelKeyPress += (sender, eventArgs) => {
eventArgs.Cancel = true;
tokenSource.Cancel();
};
var task = Task.Run(() => foo(tokenSource.Token));
task.Wait();
WaitFor(action: "exit");
}
private static void foo(CancellationToken token)
{
const int Times = 10;
for (var x = 0; x < Times && token.IsCancellationRequested == false; ++x) {
Console.WriteLine("Important work");
Task
.Delay(200)
.Wait();
}
Console.WriteLine($"Free resources: {token.IsCancellationRequested}");
}
public static void WaitFor(ConsoleKey consoleKey = ConsoleKey.Escape, string action = "continue")
{
Console.Write($"Press {consoleKey} to {action} ...");
var consoleKeyInfo = default(ConsoleKeyInfo);
do {
consoleKeyInfo = Console.ReadKey(true);
}
while (Equals(consoleKeyInfo.Key, consoleKey) == false);
Console.WriteLine();
}
BR
incureforce
The code you're spinning up in a task should be responsible of taking into account cancellation. And the cancellation token you pass into the "Task.Run" method will be used only to cancel non-started tasks.

C# 5 Async Await .Task.Factory.StartNew cancellation

I have the async code that implements cancellation token. It's working but Im not pretty sure if this is the right way to do it so I just want feedback about it.
Here is the actual code:
/// <summary>
///
/// </summary>
private async void SaveData() {
if (GetActiveServiceRequest() != null)
{
var tokenSource = new System.Threading.CancellationTokenSource();
this.ShowWizardPleaseWait("Saving data...");
var someTask = System.Threading.Tasks.Task<bool>.Factory.StartNew(() =>
{
bool returnVal = false;
// Set sleep of 7 seconds to test the 5 seconds timeout.
System.Threading.Thread.Sleep(7000);
if (!tokenSource.IsCancellationRequested)
{
// if not cancelled then save data
App.Data.EmployeeWCF ws = new App.Data.EmployeeWCF ();
returnVal = ws.UpdateData(_employee.Data);
ws.Dispose();
}
return returnVal;
}, tokenSource.Token);
if (await System.Threading.Tasks.Task.WhenAny(someTask, System.Threading.Tasks.Task.Delay(5000)) == someTask)
{
// Completed
this.HideWizardPleaseWait();
if (someTask.Result)
{
this.DialogResult = System.Windows.Forms.DialogResult.OK;
}
else
{
this.DialogResult = System.Windows.Forms.DialogResult.Abort;
}
btnOK.Enabled = true;
this.Close();
}
else
{
tokenSource.Cancel();
// Timeout logic
this.HideWizardPleaseWait();
MessageBox.Show("Timeout. Please try again.")
}
}
}
Does async / await / cancellation code is well implemented?
Thanks and appreciate the feedback.
In general, you should use ThrowIfCancellationRequested. That will complete the returned Task in a canceled state, rather than in a "ran to completion successfully" state with a false result.
Other points:
Avoid async void. This should be async Task unless it's an event handler.
Prefer Task.Run over TaskFactory.StartNew.
Use using.
If you're just using CancellationTokenSource as a timeout, then it has special capabilities for that. Creating a separate task via Task.Delay and Task.WhenAny isn't necessary.
Here's what the updated code would look like:
private async Task SaveData()
{
if (GetActiveServiceRequest() != null)
{
var tokenSource = new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(5));
var token = tokenSource.Token;
this.ShowWizardPleaseWait("Saving data...");
var someTask = System.Threading.Tasks.Task.Run(() =>
{
// Set sleep of 7 seconds to test the 5 seconds timeout.
System.Threading.Thread.Sleep(7000);
// if not cancelled then save data
token.ThrowIfCancellationRequested();
using (App.Data.EmployeeWCF ws = new App.Data.EmployeeWCF())
{
return ws.UpdateData(_employee.Data);
}
}, token);
try
{
var result = await someTask;
// Completed
this.HideWizardPleaseWait();
if (result)
{
this.DialogResult = System.Windows.Forms.DialogResult.OK;
}
else
{
this.DialogResult = System.Windows.Forms.DialogResult.Abort;
}
btnOK.Enabled = true;
this.Close();
}
catch (OperationCanceledException)
{
// Timeout logic
this.HideWizardPleaseWait();
MessageBox.Show("Timeout. Please try again.")
}
}
}

Categories