C#: Task cancellation not working (CancellationTokenSource) - c#

I have some long running code that I would like to run as a Task and cancel when needed using CancellationTokenSource but cancellation doesn't seem to work as my task keeps running when tokenSource.Cancel() is called (no exception thrown).
Probably missing something obvious?
Cut down example below:
bool init = false;
private void Button1_Click(object sender, EventArgs e)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
Task task = new Task(() =>
{
while (true)
{
token.ThrowIfCancellationRequested();
if (token.IsCancellationRequested)
{
Console.WriteLine("Operation is going to be cancelled");
throw new Exception("Task cancelled");
}
else
{
// do some work
}
}
}, token);
if (init)
{
tokenSource.Cancel();
button1.Text = "Start again";
init = false;
} else
{
try
{
task.Start();
} catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
button1.Text = "Cancel";
init = true;
}
}

Main issue in your code is that you don't store a tokenSource. Second Button1_Click invocation cancels different token than you pass to task during first call.
Second issue is that you create over and over again new task, but your logic suggest that you want one task which should be created on first click and terminated during second click.

Related

How can I cancel first ongoing click event when user click on it again?

I have a button click event handler in which I need to have 3 sec delay to make some flag true ..so it takes time to completely execute the function now meantime if the user click on the button again then this is making flag true for second click also...so I want to cancel the first click event as soon as I receive another click request.
This is my code :
private async void ClickEventHandler(ClickEvent obj)
{
int indexOfSelectedItem = this.List.IndexOf(this.List.FirstOrDefault(x => Convert.ToDouble(x.Value) == obj.Value));
if (indexOfSelectedItem > -1)
{
for (int i = 0; i < indexOfSelectedItem; i++)
{
var item = this.List.ElementAtOrDefault(0);
this.List.RemoveAt(0);
this.List.Add(item);
}
this.IsScrollEnabled = false;
await Task.Delay(3000);
this.IsScrollEnabled = true;
}
}
Yes I need to cancel the execution of the first method and call the method again ..so that it will wait for 3 sec after clicking it on second time
A simple example with a cancellation token:
private CancellationTokenSource tokenSource = new();
private async void ClickEventHandler(ClickEvent obj)
{
// Since this method is called from the clicker,
// it always starts on the main thread. Therefore,
// there is no need for additional Thread-Safe.
tokenSource.Cancel();
tokenSource = new();
CancellationToken token = tokenSource.Token;
int indexOfSelectedItem = this.List.IndexOf(this.List.FirstOrDefault(x => Convert.ToDouble(x.Value) == obj.Value));
if (indexOfSelectedItem > -1)
{
for (int i = 0; i < indexOfSelectedItem; i++)
{
var item = this.List.ElementAtOrDefault(0);
this.List.RemoveAt(0);
this.List.Add(item);
}
this.IsScrollEnabled = false;
try
{
// If the cancellation is during the Delay, then an exception will be exited.
await Task.Delay(3000, token);
this.IsScrollEnabled = true;
}
catch (Exception)
{
// Here, if necessary, actions in case the method is canceled.
}
}
}
P.S. In the example, the token is checked only in the Delay(...) method. If you need to check somewhere else, then insert a call to the method token.ThrowIfCancellationRequested(); into this place.
you could change a variable when the button gets clicked and change it back after it is done and then add an if statement checking the variable
As like xceing said , you can have a variable to check if already clicked and yet to complete the process.
Here is a sample
//keep a variable to check already clicked and yet to complete the action
bool clickEventInProgress = false;
private async void ClickEventHandler(ClickEvent obj)
{
//check it before start processing click action
if (!clickEventInProgress)
{
clickEventInProgress = true;
int indexOfSelectedItem = this.List.IndexOf(this.List.FirstOrDefault(x => Convert.ToDouble(x.Value) == obj.Value));
if (indexOfSelectedItem > -1)
{
for (int i = 0; i < indexOfSelectedItem; i++)
{
var item = this.List.ElementAtOrDefault(0);
this.List.RemoveAt(0);
this.List.Add(item);
}
this.IsScrollEnabled = false;
await Task.Delay(3000);
this.IsScrollEnabled = true;
}
clickEventInProgress = false;
}
}
you can make the variable false, one the process completed . So that next click operation will work fine.
It's not clear what exactly you are doing. A general solution could execute the cancellable task using Task.Run and then cancel it using a CancellationTokenSource. It's important to pass the associated CancellationToken to the Task API (and any asynchronous API that supports cancellation in general) too in order to enable full cancellation support e.g. cancellation of Task.Delay:
MainWindow.xaml
<Window>
<Button Content="Go!"
Click="OnClick" />
</Window>
MainWindow.xaml.cs
partial class MainWindow : Window
{
private CancellationTokenSource CancellationTokenSource { get; set; }
private SemaphoreSlim Semaphore { get; } = new SemaphoreSlim(1, 1);
public MainWindow() => InitializeComponent();
private async void OnClick(object sender, RoutedEventArgs e)
{
// If there is nothing to cancel, the reference is NULL
this.CancellationTokenSource?.Cancel();
// Wait for the previous operation to be cancelled.
// If there is nothing to cancel the SemaphoreSlim has a free slot
// and the execution continues.
await this.Semaphore.WaitAsync();
try
{
using (this.CancellationTokenSource = new CancellationTokenSource())
{
await RunCancellableOperationAsync(this.CancellationTokenSource.Token);
}
}
catch (OperationCanceledException)
{
// Invalidate disposed object to make it unusable
this.CancellationTokenSource = null;
}
finally // Cancellation completed
{
this.Semaphore.Release();
}
}
private async Task RunCancellableOperationAsync(CancellationToken cancellationToken)
{
// Execute blocking code concurrently to enable cancellation
await Task.Run(() =>
{
for (int index = 0; index < 1000; index++)
{
// Abort the iteration if requested
cancellationToken.ThrowIfCancellationRequested();
// Simulate do something
Thread.Sleep(5000);
}
}, cancellationToken);
// Support cancellation of the delay
await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken);
}
}

CancellationTokenSource.Cancel method not changing CancellationToken.IsCancellationRequested property

Anyone know what I'm doing wrong?
Windows 10 - VS2013
C# .NET 4.5.1
I have a button called testButton and a testButton_Click method that I'm trying to use to do work, but also be able to abort that work. Doing the work in a task is working, and the logic for aborting is being executed (the button turns back to a grey "Run Test"), but the cts.Cancel method doesn't seem to affect the token's IsCancellationRequested flag, or the task has the wrong token, or something...?
public System.Threading.Tasks.Task DoWorkAsync(CancellationToken ct = default(CancellationToken))
{
System.Threading.Tasks.Task T1 = new System.Threading.Tasks.Task(() =>
{
try
{
var capturedToken = ct;
// do work here
for (int i = 0; i < work.Length; i++)
{
ct.ThrowIfCancellationRequested();
capturedToken.ThrowIfCancellationRequested();
if (ct.IsCancellationRequested) throw new OperationCanceledException(ct);
// sleep 1 second
System.Threading.Thread.Sleep(1000);
}
}
catch
{
MessageBox.Show("Caught :) I can do stuff", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
taskRunning = false;
}
}, ct);
return T1;
}
private async void testButton_Click(object sender, EventArgs e)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
var task = DoWorkAsync(ct);
if (!taskRunning)
{
Cursor.Current = Cursors.WaitCursor;
testButton.BackColor = Color.Red;
testButton.Text = "ABORT";
taskRunning = true;
task.Start();
await task;
// Release cursor when setup is complete
Cursor.Current = Cursors.Default;
}
else
{
cts.Cancel();
testButton.Text = "Run Test";
testButton.BackColor = Color.Gray;
}
}

Task does not capture keystrokes

This is partly a keystroke/focus question and Task/Sleep question I put some sample code to show what I do. The exact computing is slightly different and needs more resource. This code doesn't work because no key is catched while in the loop. The only way I can get it to work is to put a MessageBox in the loop. How can I make this work? I was thinking about a sleep or a doevents but I couldn't get it to work.
CancellationTokenSource cts;
public async Task<bool> Execute()
{
globalKeyboardHook gkh = new globalKeyboardHook();
gkh.HookedKeys.Add(Keys.Escape);
gkh.KeyUp += new KeyEventHandler(gkh_KeyUp);
cts = new CancellationTokenSource();
try
{
bool reponses = await DoLongLoopingTask(someData,cts.Token);
return true;
}
catch (OperationCanceledException)
{
MessageBox.Show("Stop By User"); }
catch (Exception)
{
MessageBox.Show("Internal Error");
}
cts=null;
return true;
}
void gkh_KeyUp(object sender, KeyEventArgs e)
{
MessageBox.Show("Escape Pressed");
if (cts != null)
{
cts.Cancel();
}
}
async Task<bool> DoLongLoopingTask(List<Object> someData, CancellationToken ct)
{
int resultCuml=0;
foreach (Object data in someData)
{
ct.ThrowIfCancellationRequested();
resultCuml+=data*11;
}
return resultCuml>1012824;
}
What I really want is a Some part of code that can be interrupted while running in background. Task may or may not be the good solution if something else is better I am open to change it.

Canceled task also appears as completed

I'm playing around with async-await and cancellation to get some more understanding on the matter. For this I have made the following console application:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncTest
{
class Program
{
private static CancellationTokenSource _cancellationTokenSource;
private static CancellationToken _cancellationToken;
static void Main(string[] args)
{
Console.CancelKeyPress += myHandler;
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
var task = DoWorkAsync(_cancellationToken).ContinueWith(ContinueMethod);
task.Wait();
Console.ReadLine();
}
protected static void myHandler(object sender, ConsoleCancelEventArgs args)
{
if (_cancellationToken.CanBeCanceled)
{
_cancellationTokenSource.Cancel();
}
args.Cancel = true;
}
static void ContinueMethod(Task task)
{
if (task.IsCanceled)
{
Console.WriteLine("The task was canceled");
}
if (task.IsCompleted)
{
Console.WriteLine("The task completed successfully");
}
if (task.IsFaulted)
{
if (task.Exception != null)
{
var exceptions = task.Exception.Flatten().InnerExceptions;
foreach (var exception in exceptions)
{
Console.WriteLine(exception.Message);
}
}
Console.WriteLine("The task failed");
}
}
static async Task DoWorkAsync(CancellationToken cancellationToken)
{
await Task.Run(() => DoWork(cancellationToken), cancellationToken);
}
static void DoWork(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine("DoWork() is started");
// Uncomment the following line of code to put the task in a 'faulted' state
//throw new Exception();
for (var count = 0; count < 10; count++)
{
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Get a cancelation request");
cancellationToken.ThrowIfCancellationRequested();
}
else
{
Thread.Sleep(500);
Console.WriteLine("Count : " + count);
}
}
Console.WriteLine("DoWork() is finished");
}
}
}
When I let the application complete, I correctly receive the "The task completed successfully" message.
Now when I press CTRL+C, which triggers a cancel on the started task (see interception through myHandler), I correctly get the "The task was canceled" message. But I also get the "The task completed successfully" message. I was not expecting the task to also show up as complete, since I canceled it.
In case I uncomment the throw new Exception(); line in the DoWork() method, I correctly receive the "The task failed" message, but also the "The task completed successfully" message.
Am I wrong in my assumption and is this as designed? Or am I missing something else entirely?
I could off course work around this by adding an additional check as follows:
if (task.IsCompleted && !task.IsCanceled)
{
Console.WriteLine("The task completed successfully");
}
But I'm not sure if this is the correct way or if something else in my program is causing this completed state.
Thanks in advance for your input and/or clarification on this matter.
The documentation of Task.IsCompleted says
IsCompleted will return true when the task is in one of the three final states: RanToCompletion, Faulted, or Canceled.
So IsCompleted tells you at least that the Task is not running any more. It does not indicate if the Task completed successfully, failed or was cancelled.
Use Task.IsCompletedSuccessFully

Modal Progress Form showing IProgress and supporting Cancellation of async Task for WinForms

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;
}

Categories

Resources