I am trying to cancel a long-running task which is simulated bay an infinite loop printing to the console. Cancellation token is invoked from the main thread after 2 seconds. Even though the output on the console says "token IsCancellationRequested true", the loop continues. What is the problem here?
private static void CancelingTasks2()
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
t = Task.Factory.StartNew(() =>
{
if (token.IsCancellationRequested)
throw new OperationCanceledException("cancelled on the token", token);
print("printing for ever ...");
}, token);
Thread.Sleep(2000);
cts.Cancel();
Console.WriteLine("canceled");
Console.WriteLine("task status " + t.Status);
Console.WriteLine("token IsCancellationRequested " + token.IsCancellationRequested);
}
private static void print(string txt)
{
while (true)
{
Console.WriteLine(txt); Thread.Sleep(500);
}
}
Use Task.Run instead of Task.Factory.StartNew and try to avoid mixing Task and Thread.Sleep. Use Task.Delay. If using Task then the code needs to be async all the way.
Your loop continues because there is nothing to break out of the loop.
A rewrite of the above example with proper syntax would look like this
public class Program {
public static async Task Main(string[] args) {
Console.WriteLine("Hello");
await CancelingTasks2();
Console.WriteLine("Exit");
}
private static async Task CancelingTasks2() {
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
var t = print("printing for ever ...", token);
await Task.Delay(2000);
cts.Cancel();
Console.WriteLine("canceled");
Console.WriteLine("task status " + t.Status);
Console.WriteLine("token IsCancellationRequested " + token.IsCancellationRequested);
}
private static async Task print(string txt, CancellationToken token) {
while (true) {
if (token.IsCancellationRequested)
throw new OperationCanceledException("cancelled on the token", token);
Console.WriteLine(txt);
await Task.Delay(500);
}
}
}
And produce the following output when run
Hello
printing for ever ...
printing for ever ...
printing for ever ...
printing for ever ...
printing for ever ...
canceled
task status WaitingForActivation
token IsCancellationRequested True
Exit
Fiddle
Looks like you do an infinite loop in print, not checking the token cancellation more than once.
You should put you if inside the while loop:
while (true)
{
if (token.IsCancellationRequested)
{
throw new OperationCanceledException(
"cancelled on the token", token);
}
Console.WriteLine(txt);
Thread.Sleep(500);
}
Related
In the following code from the answer of What's a good way to run periodic tasks using Rx, with a single concurrent execution restriction?,
void Main()
{
var timer = Observable.Interval(TimeSpan.FromMilliseconds(100));
using (timer.Do(x => Console.WriteLine("!")).Subscribe(tick => DoSomething()))
{
Console.ReadLine();
}
}
private void DoSomething()
{
Console.Write("<");
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff"));
Thread.Sleep(1000);
Console.WriteLine(">");
}
I'm trying to add cancellation and test stopping the program after five seconds.
using System.Reactive.Linq;
Task DoSomething(CancellationToken cancellationToken=default)
{
if (cancellationToken.IsCancellationRequested) { return Task.CompletedTask; }
Console.Write("<");
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff"));
Thread.Sleep(1000);
Console.WriteLine(">");
return Task.CompletedTask;
}
async Task WaitAndThenCancel(int seconds, CancellationTokenSource cancellationTokenSource)
{
await Task.Delay(seconds*1000);
Console.WriteLine("Cancelling...");
cancellationTokenSource.Cancel();
}
void Main(CancellationToken cancellationToken=default)
{
var timer = Observable.Interval(TimeSpan.FromMilliseconds(100));
using (timer. Do(x =>
{
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Canceled - Main");
return; // Need to stop the stream here
}
Console.WriteLine("!");
}).Subscribe(async tick => await DoSomething(cancellationToken)))
{
Console.ReadLine();
}
}
var ct = new CancellationTokenSource();
WaitAndThenCancel(5, ct);
Main(ct.Token);
I expect the code to print the current time for N seconds, and then print "Canceled - Main" and stop. However, it starts to print "Canceled - Main" after N seconds and never stop?
!
<15:00:23.823>
!
<15:00:24.836>
!
<15:00:25.853>
!
<15:00:26.860>
!
<15:00:27.863Cancelling...
>
Canceled - Main
Canceled - Main
Canceled - Main
Canceled - Main
Canceled - Main
Canceled - Main
Canceled - Main
Canceled - Main
Canceled - Main
Canceled - Main
Canceled - Main
Canceled - Main
Canceled - Main
....
Using TakeUntil().
using System.Reactive;
using System.Reactive.Linq;
async Task DoSomething(CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
{
return; // Task.CompletedTask;
}
Console.Write("<");
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff"));
await Task.Delay(1000); // Thread.Sleep(1000);
Console.WriteLine(">");
}
async Task Main3(CancellationToken cancellationToken = default)
{
var timer = Observable.Interval(TimeSpan.FromMilliseconds(100));
var cancel = Observable.Create<Unit>(observer => cancellationToken.Register(() => {
// observer.OnNext(default);
observer.OnCompleted(); }));
using (timer.Do(x =>
{
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Canceled - Main");
return;
}
Console.WriteLine("do!");
})
.TakeUntil(Observable.Timer(TimeSpan.FromSeconds(5.0)))
.TakeUntil(cancel)
.Select(_ => Observable.FromAsync(() => DoSomething(cancellationToken)))
.Concat()
.Subscribe())
{
Console.WriteLine("Will wait for timed cancelation here.");
try
{
await Task.Delay(Timeout.Infinite, cancellationToken);
}
catch (OperationCanceledException)
{
Console.WriteLine($">{Environment.NewLine}Canceled - Main. In Using");
}
}
}
var ct = new CancellationTokenSource();
ct.CancelAfter(5000);
await Main3(ct.Token);
If you want to run an observable and stop it after a set time interval then you should simply use .TakeUntil(Observable.Timer(TimeSpan.FromSeconds(5.0))).
var timer = Observable.Interval(TimeSpan.FromMilliseconds(100));
var subscription =
timer
.Do(x => Console.WriteLine("!"))
.TakeUntil(Observable.Timer(TimeSpan.FromSeconds(5.0)))
.Subscribe(tick => DoSomething());
using (subscription)
{
Console.ReadLine();
}
If you want to use a CancellationToken then you could use this:
var timer = Observable.Interval(TimeSpan.FromMilliseconds(100));
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
var cancel = Observable.Create<Unit>(observer => cts.Token.Register(() => { observer.OnNext(default); observer.OnCompleted(); }));
var subscription =
timer
.Do(x => Console.WriteLine("!"))
.TakeUntil(cancel)
.Subscribe(tick => DoSomething());
using (subscription)
{
await Task.Delay(TimeSpan.FromSeconds(5.0));
cts.Cancel();
Console.ReadLine();
}
Your Main is not canceling subscription cause Console.ReadLine(); prevents it from it. I suggest slightly different approach:
async Task Main(CancellationToken cancellationToken)
{
var timer = Observable.Interval(TimeSpan.FromMilliseconds(100));
using (timer.Do(x =>
{
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Canceled - Main");
return;
}
Console.WriteLine("!");
}).Subscribe(async tick => await DoSomething(cancellationToken)))
{
Console.WriteLine("Will wait for timed cancelation here.");
try
{
await Task.Delay(Timeout.Infinite, cancellationToken);
}
catch (OperationCanceledException)
{
Console.WriteLine($">{Environment.NewLine}Canceled - Main. In Using");
}
}
}
var ct = new CancellationTokenSource();
ct.CancelAfter(5000); // also no need to reinvent the wheel, just use the build in method for timed cancelation
await Main(ct.Token);
One more note Subscribe is not task-aware, i.e. it will not handle "truly" async methods correctly. For example if you change the DoSomething method to the following:
async Task DoSomething(CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
Console.Write("<");
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff"));
await Task.Delay(1000);
Console.WriteLine(">");
}
The result will differ from what is expected/wanted. One approach to handle that is:
using (timer.Do(x =>
{
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Canceled - Main");
return;
}
Console.WriteLine("!");
})
.Select(_ => Observable.FromAsync(() => DoSomething(cancellationToken)))
.Concat()
.Subscribe())
{
// ...
}
I have this example:
var asyncTasks = new Task[list.Length];
for (int i = 0; i< list.Length; i++)
{
asyncTasks[i] = tf(cts.Token, list[i]);
}
await Task.WhenAll(asyncTasks);
//do other stuffs when all tasks completed
and of course i have my async function called tf:
private async Task tf(CancellationToken token , string list)
{
try
{
await Task.Run(() =>
{
if (token.IsCancellationRequested)
{
MessageBox.Show("Stopped", "Operation Aborted");
token.ThrowIfCancellationRequested();
cts = new CancellationTokenSource();
}
// do something
}, token);
}catch{}
cts = new CancellationTokenSource();
}
EDIT: in my cancel button i declare: cts.Cancel();
This method works properly for me because it executes an array of tasks at the same time, but in my opinion I'm not able to send token for cancellation request because all cts.Token already assigned are valid, so if in another button i try to cts.Cancel() this won't work. How can i do the same but making cancellation token request valid?
The problem was that i reset the token for every call with cts = new CancellationTokenSource(); at the end of task function
Function fixed:
private async Task tf(CancellationToken token , string list)
{
try
{
await Task.Run(() =>
{
if (token.IsCancellationRequested)
{
MessageBox.Show("Stopped", "Operation Aborted");
token.ThrowIfCancellationRequested();
cts = new CancellationTokenSource();
}
// do something
}, token);
}catch{}
}
I want to run two tasks.
StartAccessTokenTimer() runs every 60 seconds and refreshes accesstoken variable.
StartItemsTimer() will start after StartAccessTokenTimer() and work every 3 seconds if access token get.
private accessToken = "";
private async Task StartAccessTokenTimer()
{
CancellationTokenSource source = new CancellationTokenSource();
while (true)
{
accesstoken = await GetAccessToken();
await Task.Delay(TimeSpan.FromSeconds(3), source.Token);
}
}
private async Task StartItemsTimer()
{
CancellationTokenSource source = new CancellationTokenSource();
while (true)
{
var items = await GetItems(accessToken, "1");
await Task.Delay(TimeSpan.FromSeconds(60), source.Token);
}
}
public async Task StartOperations(){
await StartAccessTokenTimer();
await StartItemsTimer();
}
But it does not filre GetItems() methot. Because StartAccessTokenTimer() never start.. It fires GetAccessToken() continiously.
To trigger them to fire at the same time you can do the following:
public async Task StartOperations()
{
await Task.WhenAll(new Task[]
{
StartAccessTokenTimer(),
StartItemsTimer()
});
}
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
As you can see in this code:
public async void TaskDelayTest()
{
while (LoopCheck)
{
for (int i = 0; i < 100; i++)
{
textBox1.Text = i.ToString();
await Task.Delay(1000);
}
}
}
I want it to set textbox to string value of i with one second period until I set LoopCheck value to false . But what it does is that it creates all iteration ones in for all and even if I set LoopCheck value to false it still does what it does asyncronously.
I want to cancel all awaited Task.Delay() iteration when I set LoopCheck=false. How can I cancel it?
Use the overload of Task.Delay which accepts a CancellationToken
public async Task TaskDelayTest(CancellationToken token)
{
while (LoopCheck)
{
token.throwIfCancellationRequested();
for (int i = 0; i < 100; i++)
{
textBox1.Text = i.ToString();
await Task.Delay(1000, token);
}
}
}
var tokenSource = new CancellationTokenSource();
TaskDelayTest(tokenSource.Token);
...
tokenSource.Cancel();
If you're going to poll, poll on a CancellationToken:
public async Task TaskDelayTestAsync(CancellationToken token)
{
for (int i = 0; i < 100; i++)
{
textBox1.Text = i.ToString();
await Task.Delay(TimeSpan.FromSeconds(1), token);
}
}
For more information, see the cancellation documentation.
Just a slight comment about having a cancellation token, and using a try-catch to stop it throwing an exception - your iteration block might fail due to a different reason, or it might fail due to a different task getting cancelled (e.g. from an http request timing out in a sub method), so to have the cancellation token not throw an exception you might want a bit more complicated catch block
public async void TaskDelayTest(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
for (int i = 0; i < 100; i++)
{
try
{
textBox1.Text = i.ToString();
await DoSomethingThatMightFail();
await Task.Delay(1000, token);
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
//task is cancelled, return or do something else
return;
}
catch(Exception ex)
{
//this is an actual error, log/throw/dostuff here
}
}
}
}
After running into this problem I wrote a drop in replacement that behaves as expected if you want to do polling:
public static class TaskDelaySafe
{
public static async Task Delay(int millisecondsDelay, CancellationToken cancellationToken)
{
await Task.Delay(TimeSpan.FromMilliseconds(millisecondsDelay), cancellationToken);
}
public static async Task Delay(TimeSpan delay, CancellationToken cancellationToken)
{
var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var task = new TaskCompletionSource<int>();
tokenSource.Token.Register(() => task.SetResult(0));
await Task.WhenAny(
Task.Delay(delay, CancellationToken.None),
task.Task);
}
}
It uses a cancellation token callback to complete a task and then awaits either that synthetic task or the normal Task.Delay with no cancellation token. This way it won't throw an exception when the source token is cancelled, but still responds to the cancellation by returning execution. You still need to check the IsCancellationRequested after calling it to decide what to do if it is cancelled.
Unit tests, if anyone is interested:
[Test]
public async Task TaskDelay_WaitAlongTime()
{
var sw = System.Diagnostics.Stopwatch.StartNew();
await Base.Framework.TaskDelaySafe.Delay(System.TimeSpan.FromSeconds(5), System.Threading.CancellationToken.None);
Assert.IsTrue(sw.Elapsed > System.TimeSpan.FromSeconds(4));
}
[Test]
public async Task TaskDelay_DoesNotWaitAlongTime()
{
var tokenSource = new System.Threading.CancellationTokenSource(250);
var sw = System.Diagnostics.Stopwatch.StartNew();
await Base.Framework.TaskDelaySafe.Delay(System.TimeSpan.FromSeconds(5), tokenSource.Token);
Assert.IsTrue(sw.Elapsed < System.TimeSpan.FromSeconds(1));
}
[Test]
public async Task TaskDelay_PrecancelledToken()
{
var tokenSource = new System.Threading.CancellationTokenSource();
tokenSource.Cancel();
var sw = System.Diagnostics.Stopwatch.StartNew();
await Base.Framework.TaskDelaySafe.Delay(System.TimeSpan.FromSeconds(5), tokenSource.Token);
Assert.IsTrue(sw.Elapsed < System.TimeSpan.FromSeconds(1));
}
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static DateTime start;
static CancellationTokenSource tokenSource;
static void Main(string[] args)
{
start = DateTime.Now;
Console.WriteLine(start);
TaskDelayTest();
TaskCancel();
Console.ReadKey();
}
public static async void TaskCancel()
{
await Task.Delay(3000);
tokenSource?.Cancel();
DateTime end = DateTime.Now;
Console.WriteLine(end);
Console.WriteLine((end - start).TotalMilliseconds);
}
public static async void TaskDelayTest()
{
tokenSource = new CancellationTokenSource();
try
{
await Task.Delay(2000, tokenSource.Token);
DateTime end = DateTime.Now;
Console.WriteLine(end);
Console.WriteLine((end - start).TotalMilliseconds);
}
catch (TaskCanceledException ex)
{
Console.WriteLine(ex.Message);
}
finally
{
tokenSource.Dispose();
tokenSource = null;
}
}
}
}