Task cancellation based on Task ID - c#

.Net 4.5
I have a list of tasks
List<Task> lTasks
each task has its own
'CancellationTokenSource' and 'cancellationToken'
The
Task.Id and Task.status
are listed in a DataGridView.
Each row in the DataGridView also has a button called 'StopTask'.
If user clicked on 'StopTask'button, that specific task should be cancelled based on
Task.Id
How can I do something like that?
Here is my method that creates a new Task every time it is called
private void CreateNewTask(int RowIndex)
{
CancellationTokenSource cts = new CancellationTokenSource(); //Init new CancellationTokenSource
var cancellationToken = cts.Token;
try
{
var t = Task.Factory.StartNew(() =>
{
Console.WriteLine("Start new task");
for (int i = 0; i < 100; i++)
{
dataGridView_TaskList.Rows[RowIndex].Cells["OutputValue1"].Value = i;
System.Threading.Thread.Sleep(1000);
}
cancellationToken.ThrowIfCancellationRequested();
}, cancellationToken);
lTasks.Add(t); //Add to Liast of tasks (List<Task>)
dataGridView_TaskList.Rows[RowIndex].Cells["TaskID"].Value = t.Id;
dataGridView_TaskList.Rows[RowIndex].Cells["TaskStatus"].Value = t.Status;
}
finally
{
cts.Dispose();
}
}
UI with DataGridView
Based on the answers and comments, I did the following and it seems it works fine.
I used DataGridView as my 'Dictionary' and saved the token of each task in
dataGridView_TaskList.Rows[RowIndex].Cells["RowIndex"].Tag = cts;
I also used
RowIndex
as a unique ID, because two tasks may have same TaskID.
New tasks are created as shown below:
private async void CreateNewTask(int RowIndex)
{
CancellationTokenSource cts = new CancellationTokenSource(); //Init new CancellationTokenSource
var cancellationToken = cts.Token;
try
{
Task t = Task.Run(async () => //Task.Run automatically unwraps nested Task types!
{
try
{
Console.WriteLine("Start new task");
for (int i = 0; i < 100; i++)
{
// inside this loop the Token cancellation has no effect, it will keep running until this loop is finished then it will throw a cancellation exception
dataGridView_TaskList.Rows[RowIndex].Cells["OutputValue1"].Value = i;
await Task.Delay(300);
}
cancellationToken.ThrowIfCancellationRequested();
}
catch (OperationCanceledException ex)
{
ShowMsgBox.Show(ex.Message, "Cancelation", enumMessageIcon.Information, enumMessageButton.OK);
}
}, cancellationToken);
lTasks.Add(t); //Add to Liast of tasks (List<Task>)
dataGridView_TaskList.Rows[RowIndex].Cells["TaskID"].Value = t.Id; //The TaskId is not guaranteed to be unique
dataGridView_TaskList.Rows[RowIndex].Cells["TaskStatus"].Value = t.Status;
dataGridView_TaskList.Rows[RowIndex].Cells["RowIndex"].Value = RowIndex;
dataGridView_TaskList.Rows[RowIndex].Cells["RowIndex"].Tag = cts;
}
catch (Exception ex)
{
ShowMsgBox.Show(ex.Message, "Exception", enumMessageIcon.Error, enumMessageButton.OK);
}
}
and task can be cancelled as shown below
private void dataGridView_TaskList_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
var senderGrid = (DataGridView)sender;
if (senderGrid.Columns[e.ColumnIndex] is DataGridViewButtonColumn &&
e.RowIndex >= 0)
{
if (senderGrid.Columns[e.ColumnIndex].Name is "StopTask")
{
//TODO - Button Clicked - Execute Code Here
int.TryParse(dataGridView_TaskList.Rows[e.RowIndex].Cells["RowIndex"].Value.ToString(), out int RowIndex);
CancellationTokenSource cts_ToBeCancelled = (CancellationTokenSource)dataGridView_TaskList.Rows[e.RowIndex].Cells["RowIndex"].Tag;
cts_ToBeCancelled.Cancel();
}
}
}

Add the task ids with cancellation token source to a dictionary, from there you can pull out the cancellation token by id.
Dictionary<int,CancellationTokenSource> taskLookup = new Dictionary<int,CancellationTokenSource>();
...
var t = Task.Factory.StartNew(() =>
...
taskLookup.Add(t.Id, cts);
Also, you can't dispose of the CancellationTokenSource and then use it later to cancel.

You need to call cancellationToken.Cancel(); to cancel a taks.
However, inside your task you need to check whether it was cancelled or not. This probably means that inside your for loop you should add the following:
if (ct.IsCancellationRequested)
{
// Clean up here, then...
ct.ThrowIfCancellationRequested();
}
Note that Thread.Sleep in tasks is a bad idea as this will block on thread from the threadpool. Use await Task.Delay instead

I had the same problem and to resolve it I drived from Task class and added and identifire to MyTask class and in construction injected GUID to distinguish the task.
public class MyTask : Task
{
public Guid Identifire;
public MyTask(Action action, Guid identifire) : base(action)
{
Identifire = identifire;
}
public MyTask(Action action, CancellationToken cancellationToken, Guid identifire) : base(action, cancellationToken)
{
Identifire = identifire;
}
public MyTask(Action action, TaskCreationOptions creationOptions, Guid identifire) : base(action, creationOptions)
{
Identifire = identifire;
}
public MyTask(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions, Guid identifire) : base(action, cancellationToken, creationOptions)
{
Identifire = identifire;
}
public MyTask(Action<object> action, object state, Guid identifire) : base(action, state)
{
Identifire = identifire;
}
public MyTask(Action<object> action, object state, CancellationToken cancellationToken, Guid identifire) : base(action, state, cancellationToken)
{
Identifire = identifire;
}
public MyTask(Action<object> action, object state, TaskCreationOptions creationOptions, Guid identifire) : base(action, state, creationOptions)
{
Identifire = identifire;
}
public MyTask(Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions, Guid identifire) : base(action, state, cancellationToken, creationOptions)
{
Identifire = identifire;
}
}

Related

Cancel Async Task with CancellationTokenSource from a button click not working

What I need to do is able to cancel a task that is running async. Task should be cancelled on cancel button click. I have done it with CancellationTokenSource. But it is not working properly.
public class classA
{
CancellationTokenSource _tokenSource = null;
public void OnCancelButtonClick()
{
MessageBox.Show("Do you Really want to cancel upload");
_tokenSource = new CancellationTokenSource();
_tokenSource.Cancel();
}
public async void UploadBtnClick(object param )
{
_tokenSource = new CancellationTokenSource();
var token = _tokenSource.Token;
try
{
await Task.Run(() => UploadFunction(token));
}
catch(OperationCanceledException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
_tokenSource.Dispose();
}
}
public Task<bool> UploadFunction(CancellationToken token)
{
foreach
{
//code here
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
}
}
}
}
I am calling OnCancelButtonClick from another function
public class classB
{
public void CancelUploadBtnExecute(object param)
{
FilesViewModel vm = new FilesViewModel();
vm.OnCancelButtonClick();
}
}
when I click on OnCancelButtonClick , IsCancellationRequested is false, but not reflected inside UploadFunction so the task won't stop.
It's because you are initializing new CancellationTokenSource in OnCancelButtonClick().
Just remove this line from OnCancelButtonClick()
_tokenSource = new CancellationTokenSource();
UploadFunction method is using token created from CancellationTokenSource in UploadBtnClick method.

How to cancel Task without exception?

I need to execute a kind of LongRunning task after a delay.
Each Task can be cancelled. I prefer TPL with cancellationToken.
Since my task is long running and before starting a task it has to be placed in dictionary I have to use new Task(). But I've faced different behavior - when task is created using new Task() after Cancel() it throws TaskCanceledException whereas a task created with Task.Run doesn't throw an exception.
Generally I need to recognize the difference and not get TaskCanceledException.
It's my code:
internal sealed class Worker : IDisposable
{
private readonly IDictionary<Guid, (Task task, CancellationTokenSource cts)> _tasks =
new Dictionary<Guid, (Task task, CancellationTokenSource cts)>();
public void ExecuteAfter(Action action, TimeSpan waitBeforeExecute, out Guid cancellationId)
{
var cts = new CancellationTokenSource();
var task = new Task(async () =>
{
await Task.Delay(waitBeforeExecute, cts.Token);
action();
}, cts.Token, TaskCreationOptions.LongRunning);
cancellationId = Guid.NewGuid();
_tasks.Add(cancellationId, (task, cts));
task.Start(TaskScheduler.Default);
}
public void ExecuteAfter2(Action action, TimeSpan waitBeforeExecute, out Guid cancellationId)
{
var cts = new CancellationTokenSource();
cancellationId = Guid.NewGuid();
_tasks.Add(cancellationId, (Task.Run(async () =>
{
await Task.Delay(waitBeforeExecute, cts.Token);
action();
}, cts.Token), cts));
}
public void Abort(Guid cancellationId)
{
if (_tasks.TryGetValue(cancellationId, out var value))
{
value.cts.Cancel();
//value.task.Wait();
_tasks.Remove(cancellationId);
Dispose(value.cts);
Dispose(value.task);
}
}
public void Dispose()
{
if (_tasks.Count > 0)
{
foreach (var t in _tasks)
{
Dispose(t.Value.cts);
Dispose(t.Value.task);
}
_tasks.Clear();
}
}
private static void Dispose(IDisposable obj)
{
if (obj == null)
{
return;
}
try
{
obj.Dispose();
}
catch (Exception ex)
{
//Log.Exception(ex);
}
}
}
internal class Program
{
private static void Main(string[] args)
{
Action act = () => Console.WriteLine("......");
Console.WriteLine("Started");
using (var w = new Worker())
{
w.ExecuteAfter(act, TimeSpan.FromMilliseconds(10000), out var id);
//w.ExecuteAfter2(act, TimeSpan.FromMilliseconds(10000), out var id);
Thread.Sleep(3000);
w.Abort(id);
}
Console.WriteLine("Enter to exit");
Console.ReadKey();
}
}
UPD:
This approach also works without exception
public void ExecuteAfter3(Action action, TimeSpan waitBeforeExecute, out Guid cancellationId)
{
var cts = new CancellationTokenSource();
cancellationId = Guid.NewGuid();
_tasks.Add(cancellationId, (Task.Factory.StartNew(async () =>
{
await Task.Delay(waitBeforeExecute, cts.Token);
action();
}, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default), cts)); ;
}
The reason of the inconsistent behavior is fundamentally incorrect usage of an async delegate in the first case. The Task constructors just don't receive Func<Task> and your asynchronous delegate is always interpreted as async void not async Task in case of using with constructor. If an exception is raised in an async Task method it's caught and placed into Task object which isn't true for an async void method, in that case exception just bubbles up out of the method to a synchronization context and goes under category of unhandled exceptions (you can familiarize with details in this Stephen Cleary article). So what happens in case of using constructor: a task which is supposed to initiate asynchronous flow is created and started. Once it reaches point when Task.Delay(...) returns a promise, the task completes and it has no more relationship to anything which happens in Task.Delay continuation (you can easily check in debugger by setting a breakpoint to value.cts.Cancel() that the task object in the _tasks dictionary has status RanToCompletetion while however the task delegate essentially is still running). When a cancellation is requested the exception is raised inside the Task.Delay method and without existence of any promise object is being promoted to app domain.
In case of Task.Run the situation is different because there are overloads of this method which are able to accept Func<Task> or Func<Task<T>> and unwrap the tasks internally in order to return underlying promise instead of wrapped task which ensures proper task object inside the _tasks dictionary and proper error handling.
The third scenario despite the fact that it doesn't throw an exception it is partially correct. Unlike Task.Run, Task.Factory.StartNew doesn't unwrap underlying task to return promise, so task stored in the _tasks is just wrapper task, like in the case with constructor (again you can check its state with debugger). It however is able to understand Func<Task> parameters, so asynchronous delegate has async Task signature which allows at least to handle and store exception in the underlying task. In order to get this underlying task with Task.Factory.StartNew you need to unwrap the task by yourself with Unwrap() extension method.
The Task.Factory.StartNew isn't considered as a beast practice of creating tasks because of certain dangers related to its application (see there). It however can be used with some caveats if you need to apply specific options like LongRunning which cannot be directly applied with Task.Run.
I don't know why I got down votes here but it's inspired me to update my answer.
UPDATED
My full approach:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp4
{
internal class Program
{
private static void Main(string[] args)
{
using (var delayedWorker = new DelayedWorker())
{
delayedWorker.ProcessWithDelay(() => { Console.WriteLine("100"); }, TimeSpan.FromSeconds(5), out var cancellationId_1);
delayedWorker.ProcessWithDelay(() => { Console.WriteLine("200"); }, TimeSpan.FromSeconds(10), out var cancellationId_2);
delayedWorker.ProcessWithDelay(() => { Console.WriteLine("300"); }, TimeSpan.FromSeconds(15), out var cancellationId_3);
Cancel_3(delayedWorker, cancellationId_3);
Console.ReadKey();
}
}
private static void Cancel_3(DelayedWorker delayedWorker, Guid cancellationId_3)
{
Task.Run(() => { delayedWorker.Abort(cancellationId_3); }).Wait();
}
internal sealed class DelayedWorker : IDisposable
{
private readonly object _locker = new object();
private readonly object _disposeLocker = new object();
private readonly IDictionary<Guid, (Task task, CancellationTokenSource cts)> _tasks = new Dictionary<Guid, (Task task, CancellationTokenSource cts)>();
private bool _disposing;
public void ProcessWithDelay(Action action, TimeSpan waitBeforeExecute, out Guid cancellationId)
{
Console.WriteLine("Creating delayed action...");
CancellationTokenSource tempCts = null;
CancellationTokenSource cts = null;
try
{
var id = cancellationId = Guid.NewGuid();
tempCts = new CancellationTokenSource();
cts = tempCts;
var task = new Task(() => { Process(action, waitBeforeExecute, cts); }, TaskCreationOptions.LongRunning);
_tasks.Add(cancellationId, (task, cts));
tempCts = null;
task.ContinueWith(t =>
{
lock (_disposeLocker)
{
if (!_disposing)
{
TryRemove(id);
}
}
}, TaskContinuationOptions.ExecuteSynchronously);
Console.WriteLine($"Created(cancellationId: {cancellationId})");
task.Start(TaskScheduler.Default);
}
finally
{
if (tempCts != null)
{
tempCts.Dispose();
}
}
}
private void Process(Action action, TimeSpan waitBeforeExecute, CancellationTokenSource cts)
{
Console.WriteLine("Starting delayed action...");
cts.Token.WaitHandle.WaitOne(waitBeforeExecute);
if (cts.Token.IsCancellationRequested)
{
return;
}
lock (_locker)
{
Console.WriteLine("Performing action...");
action();
}
}
public bool Abort(Guid cancellationId)
{
Console.WriteLine($"Aborting(cancellationId: {cancellationId})...");
lock (_locker)
{
if (_tasks.TryGetValue(cancellationId, out var value))
{
if (value.task.IsCompleted)
{
Console.WriteLine("too late");
return false;
}
value.cts.Cancel();
value.task.Wait();
Console.WriteLine("Aborted");
return true;
}
Console.WriteLine("Either too late or wrong cancellation id");
return true;
}
}
private void TryRemove(Guid id)
{
if (_tasks.TryGetValue(id, out var value))
{
Remove(id, value.task, value.cts);
}
}
private void Remove(Guid id, Task task, CancellationTokenSource cts)
{
_tasks.Remove(id);
Dispose(cts);
Dispose(task);
}
public void Dispose()
{
lock (_disposeLocker)
{
_disposing = true;
}
if (_tasks.Count > 0)
{
foreach (var t in _tasks)
{
t.Value.cts.Cancel();
t.Value.task.Wait();
Dispose(t.Value.cts);
Dispose(t.Value.task);
}
_tasks.Clear();
}
}
private static void Dispose(IDisposable obj)
{
if (obj == null)
{
return;
}
try
{
obj.Dispose();
}
catch (Exception ex)
{
//log ex
}
}
}
}
}

How to cancel a task from different class

I want to cancel a task started by one class from some other class. Below is an example program where i have two classes and want to cancel the task from different class. Cancellation token doesn't seem to work.
namespace threadingiwthcancel
{
class ThreadExample
{
public async Task PrintSomething(CancellationToken token)
{
int i = 0;
while (true)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancel requested");
break;
}
await Task.Delay(TimeSpan.FromSeconds(1));
Console.WriteLine(i);
i++;
}
}
}
class Cancel
{
public void cancelTask()
{
CancellationTokenSource source = new CancellationTokenSource();
source.Cancel();
}
}
class Program
{
static void Main(string[] args)
{
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
callasyncmethod(token);
Thread.Sleep(TimeSpan.FromSeconds(2));
Cancel c = new Cancel();
c.cancelTask();
Console.ReadLine();
}
private static async void callasyncmethod(CancellationToken token)
{
ThreadExample te = new ThreadExample();
await te.PrintSomething(token);
}
}
}
Cancellation token doesnt seem to work.
The reason for failure is that you are creating a different CancellationTokenSource and calling Cancel on it. Sadly, no other operations will be notified and so your original task continues.
I want to cancel a task started by one class from some other class
Be sure to pass the original source to it:
class Cancel
{
CancellationTokenSource _source;
public Cancel (CancellationTokenSource source)
{
_source = source;
}
public void cancelTask()
{
_source.Cancel();
}
}
static void Main(string[] args)
{
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
callasyncmethod(token);
Thread.Sleep(TimeSpan.FromSeconds(2));
Cancel c = new Cancel(source); // pass in the original source
c.cancelTask();
Console.ReadLine();
}
if you want to do like that than you need to pass object of CancellationSoruce to you cancel method in other class , do not create new CancellationSoruce
class Cancel
{
public void cancelTask(CancellationTokenSource source )
{
source.Cancel();
}
}
in main
static void Main(string[] args)
{
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
callasyncmethod(token);
Thread.Sleep(TimeSpan.FromSeconds(2));
Cancel c = new Cancel();
c.cancelTask(source );
Console.ReadLine();
}
or can do like this
class Cancel
{
private readonly CancellationTokenSource source;
public Cancel(CancellationTokenSource source)
{
this.source = soruce;
}
public void cancelTask()
{
source.Cancel();
}
}
and in main
static void Main(string[] args)
{
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
callasyncmethod(token);
Thread.Sleep(TimeSpan.FromSeconds(2));
Cancel c = new Cancel(source );
c.cancelTask();
Console.ReadLine();
}

Correct use of CancellationToken

This is my situation:
private CancellationTokenSource cancellationTokenSource;
private CancellationToken cancellationToken;
public IoTHub()
{
cancellationTokenSource = new CancellationTokenSource();
cancellationToken = cancellationTokenSource.Token;
receive();
}
private void receive()
{
eventHubClient = EventHubClient.CreateFromConnectionString(connectionString, iotHubD2cEndpoint);
var d2cPartitions = eventHubClient.GetRuntimeInformation().PartitionIds;
foreach (string partition in d2cPartitions)
{
ReceiveMessagesFromDeviceAsync(partition, cancellationToken);
}
}
private async Task ReceiveMessagesFromDeviceAsync(CancellationToken ct)
{
var eventHubReceiver = eventHubClient.GetDefaultConsumerGroup().CreateReceiver(partition, DateTime.UtcNow);
while (true)
{
if(ct.IsCancellationRequested)
{
break;
}
EventData eventData = await eventHubReceiver.ReceiveAsync();
if (eventData == null) continue;
string data = Encoding.UTF8.GetString(eventData.GetBytes());
// Javascript function with Websocket
Clients.All.setMessage(data);
}
}
public void cancelToken()
{
cancellationTokenSource.Cancel();
}
The Task will not be cancelled, when calling the cancelToken method. How come?
I have read the Microsoft guide, an other Stackoverflow questions about Task cancellation.
But still have difficulty using them correctly.
You can consider CancellationToken like a flag, indicating if a cancellation signal is received. Thus:
while (true)
{
//you check the "flag" here, to see if the operation is cancelled, correct usage
if(ct.IsCancellationRequested)
{
break;
}
//your instance of CancellationToken (ct) can't stop this task from running
await LongRunningTask();
}
If you want LongRunningTask to be cancelled, you should use CancellationToken inside the task body and check it when necessary, like this:
async Task LongRunningTask()
{
await DoPrepareWorkAsync();
if (ct.IsCancellationRequested)
{
//it's cancelled!
return;
}
//let's do it
await DoItAsync();
if (ct.IsCancellationRequested)
{
//oh, it's cancelled after we already did something!
//fortunately we have rollback function
await RollbackAsync();
}
}

Create a Task list, with tasks without executing

I have an async method
private async Task DoSomething(CancellationToken token)
a list of Tasks
private List<Task> workers = new List<Task>();
and I have to create N threads that runs that method
public void CreateThreads(int n)
{
tokenSource = new CancellationTokenSource();
token = tokenSource.Token;
for (int i = 0; i < n; i++)
{
workers.Add(DoSomething(token));
}
}
but the problem is that those have to run at a given time
public async Task StartAllWorkers()
{
if (workers.Count > 0)
{
try
{
while (workers.Count > 0)
{
Task finishedWorker = await Task.WhenAny(workers.ToArray());
workers.Remove(finishedWorker);
finishedWorker.Dispose();
}
if (workers.Count == 0)
{
tokenSource = null;
}
}
catch (OperationCanceledException)
{
throw;
}
}
}
but actually they run when i call the CreateThreads Method (before the StartAllWorkers).
I searched for keywords and problems like mine but couldn't find anything about stopping the task from running.
I've tried a lot of different aproaches but anything that could solve my problem entirely.
For example, moving the code from DoSomething into a workers.Add(new Task(async () => { }, token)); would run the StartAllWorkers(), but the threads will never actually start.
There is another method for calling the tokenSource.Cancel().
You can use TaskCompletionSource<T> as a one-time "signal" to asynchronous methods.
So you'd create it like this:
private TaskCompletionSource<object> _tcs;
public void CreateThreads(int n)
{
_tcs = new TaskCompletionSource<object>();
tokenSource = new CancellationTokenSource();
token = tokenSource.Token;
for (int i = 0; i < n; i++)
{
workers.Add(DoSomething(_tcs.Task, token));
}
}
Then when you're ready to start the tasks, just complete the "start" signal task:
public Task StartAllWorkers()
{
_tcs.TrySetCompleted(null);
return Task.WhenAll(workers);
}
(The StartAllWorkers method above has slightly different semantics than your original method: your original method would throw a cancellation exception as soon as the first task canceled; this one will wait until all the methods complete and then throw a cancellation exception)
Then your DoSomething just has to honor the "start signal":
private static async Task DoSomething(Task start, CancellationToken token)
{
await start;
... // rest of your code
}
What about this idea:
Instead of holding a list of tasks, hold a list of TaskReference items:
public class TaskReference
{
private readonly Func<Task> _func;
public TaskReference(Func<Task> func)
{
_func = func;
}
public async Task RunAsync()
{
await _func();
}
}
Adding to the list works like this:
taskList.Add(new TaskReference(() => DoSomething(myToken)));
And execution like this:
await Task.WhenAll(taskList.Select(o => o.RunAsync()));

Categories