c# async task cancellation - c#

I have some problems getting a grasp on tasks and cancellation tokens. I've made a program that looks like this:
static void Main(string[] args)
{
CancellationTokenSource token = new CancellationTokenSource();
Stopwatch stop = new Stopwatch();
stop.Start();
for (int i = 0; i < 5; i++)
{
//Thread.Sleep(1000);
Task.Factory.StartNew(() => myLongTask(token.Token, (i + 1) * 1000));
}
while (true)
{
Thread.SpinWait(1000);
if (stop.ElapsedMilliseconds > 3000)
{
token.Cancel();
}
}
}
public static void myLongTask(CancellationToken token, int time)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancelled");
return;
}
var sw = Stopwatch.StartNew();
Console.WriteLine($"Task {time / 1000} started");
while (sw.ElapsedMilliseconds < time)
Thread.SpinWait(1000);
Console.WriteLine($"Task {time / 1000} ended");
}
I am running 5 tasks simultaneously (although when I don't include Thread.Sleep() the for loop seems to run before tasks are even started?). None of the tasks get ever cancelled when I run the program. Also what is bugging me is...what task am I really cancelling when calling token.Cancel()? How can I choose which of the 5 tasks will I kill? I can define each task by it's variable, but I can't access its CancellationRequested property since it is triggered with CancellationToken. Would I need 5 different tokens then?

None of the tasks get ever cancelled when I run the program.
That's because you're only ever checking the cancellation token at the start of the task. Once it's got past that first token.IsCancellationRequested check, cancelling the token does nothing. If you move your check into your loop, like this:
while (sw.ElapsedMilliseconds < time)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancelled");
return;
}
Thread.SpinWait(1000);
}
... then you'll see the tasks react appropriately.
Also what is bugging me is...what task am I really cancelling when calling token.Cancel()?
You're not cancelling a task - you're cancelling the cancellation token. Any task that observes that cancellation token will be cancelled (or complete, or whatever action it takes) but there's no direct correlation between a task and a token.
When we talk about "cancelling a task" we really mean "cancelling a token which we believe the task is observing".

Related

Async method somehow yields control when it shouldn't

Consider the code below
static void Main(string[] args)
{
var ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;
Task<string> task = Task.Run(() =>
{
ct.ThrowIfCancellationRequested();
var task2 = ActualAsyncTask();
while (!task2.IsCompleted)
{
var t = DateTime.Now;
while (DateTime.Now - t < TimeSpan.FromSeconds(1))
{
}
ct.ThrowIfCancellationRequested();
}
return task2;
}, ct);
Console.ReadLine();
ts.Cancel();
Console.ReadLine();
}
static async Task<string> ActualAsyncTask()
{
await Task.Delay(1000);
for(int i = 0; i < 100; ++i)
{
var t = DateTime.Now;
while (DateTime.Now - t < TimeSpan.FromSeconds(1))
{
}
Console.WriteLine("tick");
}
return "success";
}
It spawns a task that busy-waits for a cancellation request while an asynchronous method also busy-waits and prints some text into console.
When you let it run for a few seconds and then press enter, the task will throw the cancellation exception. While it's an interesting trick, I don't understand how the async method is able to yield control and make it possible.
Both the anonymous lambda within the task and the asynchronous method report to run on the same worker thread, which means they should be synchronous. In my understanding this setup should not throw the cancellation exception past the first 1 second await, because past that point there are no more awaits to allow the while loop of the anonymous lambda to gain control of the thread flow, yet somehow they seemingly both run in parallel using one thread.
If I remove the await command entirely, the execution becomes as expected - sending a cancellation request no longer interrupts the task. What am I missing?

How to force an ActionBlock to complete fast

According to the documentation:
A dataflow block is considered completed when it is not currently processing a message and when it has guaranteed that it will not process any more messages.
This behavior is not ideal in my case. I want to be able to cancel the job at any time, but the processing of each individual action takes a long time. So when I cancel the token, the effect is not immediate. I must wait for the currently processed item to complete. I have no way to cancel the actions directly, because the API I use is not cancelable. Can I do anything to make the block ignore the currently running action, and complete instantly?
Here is an example that demonstrates my problem. The token is canceled after 500 msec, and the duration of each action is 1000 msec:
static async Task Main()
{
var cts = new CancellationTokenSource(500);
var block = new ActionBlock<int>(async x =>
{
await Task.Delay(1000);
}, new ExecutionDataflowBlockOptions() { CancellationToken = cts.Token });
block.Post(1); // I must wait for this one to complete
block.Post(2); // This one is ignored
block.Complete();
var stopwatch = Stopwatch.StartNew();
try
{
await block.Completion;
}
catch (OperationCanceledException)
{
Console.WriteLine($"Canceled after {stopwatch.ElapsedMilliseconds} msec");
}
}
Output:
Canceled after 1035 msec
The desired output would be a cancellation after ~500 msec.
Based on this excerpt from your comment...:
What I want to happen in case of a cancellation request is to ignore the currently running workitem. I don't care about it any more, so why I have to wait for it?
...and assuming you are truly OK with leaving the Task running, you can simply wrap the job you wish to call inside another Task which will constantly poll for cancellation or completion, and cancel that Task instead. Take a look at the following "proof-of-concept" code that wraps a "long-running" task inside another Task "tasked" with constantly polling the wrapped task for completion, and a CancellationToken for cancellation (completely "spur-of-the-moment" status, you will want to re-adapt it a bit of course):
public class LongRunningTaskSource
{
public Task LongRunning(int milliseconds)
{
return Task.Run(() =>
{
Console.WriteLine("Starting long running task");
Thread.Sleep(3000);
Console.WriteLine("Finished long running task");
});
}
public Task LongRunningTaskWrapper(int milliseconds, CancellationToken token)
{
Task task = LongRunning(milliseconds);
Task wrapperTask = Task.Run(() =>
{
while (true)
{
//Check for completion (you could, of course, do different things
//depending on whether it is faulted or completed).
if (!(task.Status == TaskStatus.Running))
break;
//Check for cancellation.
if (token.IsCancellationRequested)
{
Console.WriteLine("Aborting Task.");
token.ThrowIfCancellationRequested();
}
}
}, token);
return wrapperTask;
}
}
Using the following code:
static void Main()
{
LongRunningTaskSource longRunning = new LongRunningTaskSource();
CancellationTokenSource cts = new CancellationTokenSource(1500);
Task task = longRunning.LongRunningTaskWrapper(3000, cts.Token);
//Sleep long enough to let things roll on their own.
Thread.Sleep(5000);
Console.WriteLine("Ended Main");
}
...produces the following output:
Starting long running task
Aborting Task.
Exception thrown: 'System.OperationCanceledException' in mscorlib.dll
Finished long running task
Ended Main
The wrapped Task obviously completes in its own good time. If you don't have a problem with that, which is often not the case, hopefully, this should fit your needs.
As a supplementary example, running the following code (letting the wrapped Task finish before time-out):
static void Main()
{
LongRunningTaskSource longRunning = new LongRunningTaskSource();
CancellationTokenSource cts = new CancellationTokenSource(3000);
Task task = longRunning.LongRunningTaskWrapper(1500, cts.Token);
//Sleep long enough to let things roll on their own.
Thread.Sleep(5000);
Console.WriteLine("Ended Main");
}
...produces the following output:
Starting long running task
Finished long running task
Ended Main
So the task started and finished before timeout and nothing had to be cancelled. Of course nothing is blocked while waiting. As you probably already know, of course, if you know what is being used behind the scenes in the long-running code, it would be good to clean up if necessary.
Hopefully, you can adapt this example to pass something like this to your ActionBlock.
Disclaimer & Notes
I am not familiar with the TPL Dataflow library, so this is just a workaround, of course. Also, if all you have is, for example, a synchronous method call that you do not have any influence on at all, then you will obviously need two tasks. One wrapper task to wrap the synchronous call and another one to wrap the wrapper task to include continuous status polling and cancellation checks.

Why using ASYNC - AWAIT in task factory?

i m new in task jobs and need to help.
I have a web service,
this service will stop all tasks and start again in every 30 mins.
Q1= Is that regular code sample ?
Q2= This code works for me, but do i need ASYNC AWAIT in this project
? i'm using .net 4.0.
Thx.
private CancellationTokenSource tokenSource;
private List<Task> Tasks;
public virtual void Start()
{
// start
Tasks = new List<Task>();
tokenSource = new CancellationTokenSource();
for (int i = 0; i < 3; i++)
{
Tasks.Add(Task.Factory.StartNew(() => SomeWork(),
tokenSource.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default));
}
}
public void Stop()
{
tokenSource.Cancel();
Task.Factory.ContinueWhenAll(Tasks.ToArray(), t =>
{
Console.WriteLine("all finished");
// start again
Start();
});
}
int i = 0;
public void SomeWork()
{
while (!tokenSource.IsCancellationRequested)
{
try
{
Thread.Sleep(1000 * 4);
Console.WriteLine(Task.CurrentId + " finised!");
}
catch (Exception) { }
}
}
Do you really need to use the async/await keywords to be able to start and stop a task? No.
Should you use the async/await keywords in your web service? Not necessarily because you don't really seem to benefit much from being able to capture the context and execute the remainder of the method once a task has finisihed. Your Start method just fires off 3 tasks and return without waiting for any of the tasks to finish. So this method isn't really "awaitable" or asynchronous by nature.
You could have made use of the async/await keywords in your Stop method if you wanted to make it asynchronous, i.e. you wanted any caller of this method be able to call it asynchronously and do something once the tasks have actually been stopped:
public async Task StopAsync()
{
tokenSource.Cancel();
await Task.WhenAll(Tasks.ToArray());
Console.WriteLine("all finished");
Start();
}
await StopAsync();
//now all tasks have been stopped...do something
When using the async and await keywords the compiler basically generates a state machine for you. The beginning of an async method is executed just like any other method and when it hits an "await" keyword it returns from the method and tells the awaitable (that is the asynchronous operation) to run the remainder of the method once it has completed. Please refer to the following link for more information about this.
How and When to use `async` and `await`

Tasks being called synchronously despite being declared async

Consider the following code:
public async static Task<bool> Sleeper(int sleepTime)
{
Console.WriteLine("Sleeping for " + sleepTime + " seconds");
System.Threading.Thread.Sleep(1000 * sleepTime);
return true;
}
static void Main(string[] args)
{
Random rnd = new Random();
List<Task<bool>> tasks = new List<Task<bool>>();
Console.WriteLine("Kicking off tasks");
for (int i = 0; i < 3; i++)
{
tasks.Add(Sleeper(rnd.Next(10, 15)));
}
Console.WriteLine("All tasks launched");
Task.WhenAll(tasks);
int nComplete = 0;
foreach (var task in tasks)
{
if (task.Result)
nComplete++;
}
Console.WriteLine(nComplete + " Successful tasks");
}
Each task should sleep for a random amount of time (between 10-15 seconds). However my output looks like the following
Kicking off tasks
Sleeping for 12 seconds
Sleeping for 14 seconds
Sleeping for 12 seconds
All tasks launched
3 Successful tasks
Each "task" clearly waited for the previous task to be completed before starting (I also saw this when debugging and stepping through the code), why is this?
EDIT A lot of people have mentioned using Task.Delay which does work as expected. But what if I'm not doing anything like sleeping, just a lot of work. Consider a large do nothing loop
int s = 1;
for (int i = 0; i < 100000000000000; i++)
s *= i;
This still executes synchronously
async does not mean "runs on another thread". Stephen Toub's blog goes into a lot more detail, but under the hood the current SynchronizationContext and the operations performed determines if and when a task runs on a separate thread. In your case, Thread.Sleep doesn't do anything explicitly to run on a different thread, so it doesn't.
If you used await Task.Delay(1000 + sleepTime) instead of Thread.Sleep I think you'll find that things work as you expect, because Task.Delay is plugged into the async/await infrastructure, while Thread.Sleep isn't.
This is because you are using Thread.Sleep which is sleeping the thread which invokes Sleeper. async methods start on the thread which call them hence you are sleeping your main application thread.
In asynchronous code, you should be using Task.Delay like so:
public async static Task<bool> Sleeper(int sleepTime)
{
Console.WriteLine("Sleeping for " + sleepTime + " seconds");
await Task.Delay(1000 * sleepTime).ConfigureAwait(false);
return true;
}
async does not mean "run on another thread". I have an async intro that goes into detail about what async does mean.
In fact, Sleeper will generate a compiler warning informing you of the fact that it will run synchronously. It's a good idea to turn on "Warnings as Errors" for all new projects.
If you have CPU-bound work to do, you may run it on a thread pool thread and asynchronously wait for it to complete by using await Task.Run, as such:
public static bool Sleeper(int sleepTime);
...
for (int i = 0; i < 3; i++)
{
var sleepTime = rnd.Next(10, 15);
tasks.Add(Task.run(() => Sleeper(sleepTime)));
}

Cancellation of a task

I tried to run a simple example on the cancellation of a task like the one below
CancellationTokenSource tokenSource2 = new CancellationTokenSource();
CancellationToken token2 = tokenSource2.Token;
Task task2 = new Task(() =>
{
for (int i = 0; i < int.MaxValue; i++)
{
token2.ThrowIfCancellationRequested();
Thread.Sleep(100);
Console.WriteLine("Task 2 - Int value {0}", i);
}
}, token2);
task2.Start();
Console.WriteLine("Press any key to cancel the task");
Console.ReadLine();
tokenSource2.Cancel();
Console.WriteLine("Task 2 cancelled? {0}", task2.IsCanceled);
I expected that Console.WriteLine("Task 2 cancelled? {0}", task2.IsCanceled); would print **"Task 2 cancelled? True"**, but it printed "False".
Do you know what happened? Is that the expected behaviour? Thanks.
EDIT: to ensure that the task hasn't completed before the cancellation request is called. I added the Console.ReadLine().
First, maybe you misunderstand what IsCanceled means? It doesn't mean “this Task is pending cancellation, so it should complete shortly”, it means “this Task has been canceled, it is now complete”.
If you didn't misunderstand that, think about what exactly the sequence of events is. What happens is this:
ThrowIfCancellationRequested() is called, but the token wasn't canceled yet, so it doesn't throw.
Thread.Sleep() is called, so the thread running the Task sleeps.
Cancel() is called.
IsCanceled is checked. The code in the Task didn't have a chance to realize that the token was canceled, so it's still running, so IsCanceled returns false.
ThrowIfCancellationRequested() is called again, this time it throws, which actually cancels the Task.
This is why ISCanceled is returning false to you. If you want it to return true, you could add something like Thread.Sleep(150) before checking IsCanceled, or, even better, actually wait for the Task to complete.
The Task ended before you call for cancellation, take a look at the following below this may help to shed some more light on how to resolve your issue
By reading from here http://social.msdn.microsoft.com/Forums/da-DK/parallelextensions/thread/9f88132a-f8bd-4885-ab63-645d7b6c2127 it seems that the token is used to cancel the task BEFORE the task is "really" started, but after it has been queued.
It's more a way to cancel a task that's scheduled to occur, but not started yet. Once the task is running, the only way to cancel it is cooperatively via your own checking within the method. Without this, you'd have to always start the task, then check it internally, which would add a lot of extra, unnecessary overhead
You can even read it from Cancellation token in Task constructor: why?
This is not an answer but too long to write in a comment
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;
Task task = new Task(() =>
{
for (int i = 0; i < int.MaxValue; i++)
{
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine("Task 2 - Int value {0}", i);
}
}, cancellationToken);
task.Start();
cancellationTokenSource.Cancel();
try
{
task.Wait();
}
catch (AggregateException ae)
{
if(ae.InnerExceptions.Single() is TaskCanceledException)
Console.WriteLine("Caught TaskCanceledException");
else
Console.WriteLine("Did not catch canceled");
}
Console.WriteLine("Task 2 cancelled? {0}", task.IsCanceled);
The code above prints what is expected, and I never got a print of 'Task 2 - Int value {0}' so I don't think it finished before canceling.
Note that canceling the task throws an exception in my sample
There are some patterns for handling exceptions using tasks that can be good to read about.

Categories