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
Related
I have two instances of a class which creates a UDP socket to receive data from UDP clients. If one of the instances throws an exception I want to handle it immediately in a higher layer. In my program they're started with await Task.WhenAll(recv1.StartAsync(), recv2.StartAsync). This however waits for all tasks to finish before the first exception is thrown. Any ideas on how to resolve this problem?
static async Task Main(string[] args)
{
var udpReceiver1 = new UdpReceiver(localEndpoint1);
var udpReceiver2 = new UdpReceiver(localEndpoint2);
var cts = new CancellationTokenSource();
try
{
await Task.WhenAll(udpReceiver1.StartAsync(cts.Token), udpReceiver2.StartAsync(cts.Token));
}
catch (Exception e)
{
// Handle Exception...
cts.Cancel();
}
}
class UdpReceiver
{
public UdpReceiver(IPEndPoint endpoint)
{
udpClient = new UdpClient(endpoint);
}
public async Task StartAsync(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
var result = await ReceiveAsync(cancellationToken);
var message = Encoding.UTF8.GetString(result.Buffer);
Trace.WriteLine($"UdpClient1 received message:{Encoding.UTF8.GetString(result.Buffer)}");
// throw new Exception("UdpClient1 raising exception");
}
}
}
private async Task<UdpReceiveResult> ReceiveAsync(CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<UdpReceiveResult>();
using (cancellationToken.Register(() => tcs.TrySetCanceled(), false))
{
var task = udpClient.ReceiveAsync();
var completedTask = await Task.WhenAny(task, tcs.Task);
var result = await completedTask.ConfigureAwait(false);
return result;
}
}
private UdpClient udpClient;
}
Update 1: Task.WhenAny would be a viable solution. Thanks #CamiloTerevinto
try
{
await await Task.WhenAny(udpReceiver1.StartAsync(cts.Token), udpReceiver2.StartAsync(cts.Token));
}
catch (Exception e)
{
// Handle Exception...
cts.Cancel();
}
Update 2: For a more fine grained exception handling of all tasks I'd go with my own adapted implementation of Task.WhenAll proposed by #Servy.
The behavior is different enough from the framework WhenAll implementation that you're probably best just writing your own adapted version, fortunately it's not particularly hard to implement. Just attach a continuation to every single task, if any is cancelled or faulted, the resulting task does the same, if it succeeds store the result, and if the last task was the one to succeed, complete the task with all of the stored results.
public static Task<IEnumerable<TResult>> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks)
{
var listOfTasks = tasks.ToList();
if (listOfTasks.Count == 0)
{
return Task.FromResult(Enumerable.Empty<TResult>());
}
var tcs = new TaskCompletionSource<IEnumerable<TResult>>();
var results = new TResult[listOfTasks.Count];
int completedTasks = 0;
for (int i = 0; i < listOfTasks.Count; i++)
{
int taskIndex = i;
Task<TResult> task = listOfTasks[i];
task.ContinueWith(_ =>
{
if (task.IsCanceled)
tcs.TrySetCanceled();
else if (task.IsFaulted)
tcs.TrySetException(task.Exception.InnerExceptions);
else
{
results[taskIndex] = task.Result;
if (Interlocked.Increment(ref completedTasks) == listOfTasks.Count)
{
tcs.TrySetResult(results);
}
}
});
}
return tcs.Task;
}
As with so many task based generic operations, you also need a version without the results, and if you don't want to deal with a notable overhead, you really need to just copy-paste the result-based approach but with all of the results ripped out, which isn't hard, just inelegant. Turning all of these tasks into tasks with a result would also work, but for an operation like this the overhead is likely problematic.
public static Task WhenAll(IEnumerable<Task> tasks)
{
var listOfTasks = tasks.ToList();
if (listOfTasks.Count == 0)
{
return Task.CompletedTask;
}
var tcs = new TaskCompletionSource<bool>();
int completedTasks = 0;
for (int i = 0; i < listOfTasks.Count; i++)
{
int taskIndex = i;
Task task = listOfTasks[i];
task.ContinueWith(_ =>
{
if (task.IsCanceled)
tcs.TrySetCanceled();
else if (task.IsFaulted)
tcs.TrySetException(task.Exception.InnerExceptions);
else
{
if (Interlocked.Increment(ref completedTasks) == listOfTasks.Count)
{
tcs.TrySetResult(true);
}
}
});
}
return tcs.Task;
}
There's probably a way to do it but I can't think of one without making your code very messy. It'd be better to handle the exception in the actual task. If you need to handle it with common code, use a handler delegate.
static async Task Main(string[] args)
{
var cts = new CancellationTokenSource();
//This is our common error handler
void HandleException(Exception ex)
{
Log("Exception!" + ex.Message);
cts.Cancel();
}
var udpReceiver1 = new UdpReceiver(localEndpoint1);
var udpReceiver2 = new UdpReceiver(localEndpoint1);
//We pass the handler as one of the arguments
await Task.WhenAll(udpReceiver1.StartAsync(cts.Token, HandleException), udpReceiver2.StartAsync(cts.Token, HandleException));
}
class UdpReceiver
{
public async Task StartAsync(CancellationToken cancellationToken, Action<Exception> errorHandler)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
//Main logic goes here
}
}
catch(Exception ex)
{
errorHandler(ex); //Call common error handling code
}
}
You could await the tasks in two steps. In the first step await any of them to complete, and in case of failure initiate the cancellation. Don't handle the exception in this step. Delay the exception handling until the second step, after awaiting all of the tasks to complete. Both tasks may have failed, so you may want to handle the exception of each one separately.
Task task1 = udpReceiver1.StartAsync(cts.Token);
Task task2 = udpReceiver2.StartAsync(cts.Token);
// Await any task to complete
Task firstCompletedTask = await Task.WhenAny(task1, task2);
if (firstCompletedTask.IsFaulted) cts.Cancel();
try
{
// Await them all to complete
await Task.WhenAll(task1, task2);
}
catch
{
if (task1.IsFaulted) HandleException(task1.Exception.InnerException);
if (task2.IsFaulted) HandleException(task2.Exception.InnerException);
}
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.
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);
}
I need to call a method in action of my Task object. My task performs some kind of reading and I cancel the operation if it takes more than 2 seconds to complete.
I have this code as simulation:
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(2000));
var task = Task.Run(() =>
{
try
{
int i = 0;
while (true)
{
Thread.Sleep(500);
cts.Token.ThrowIfCancellationRequested();
Console.WriteLine("i = {0}", i);
i++;
if (i > 3) throw new InvalidOperationException();
}
}
catch (Exception e)
{
Console.WriteLine("Exception {0}", e.Message);
throw;
}
});
task.ContinueWith(t => Console.WriteLine(t.Status), TaskContinuationOptions.NotOnRanToCompletion);
My console outout is as follows:
This is what I expect and works for me. If I copy the code inside the task and create a method I no longer get the task status as Cancelled. I get status as Faulted. I must know if the operation was cancelled or an exception happened while reading process. I cannot figure out why I do not get the task status as Cancelled here.
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(2000));
var task = Task.Run(() =>
{
try
{
Tester(cts);
}
catch (Exception e)
{
Console.WriteLine("Exception {0}", e.Message);
throw;
}
});
private static void Tester(CancellationTokenSource cts)
{
int i = 0;
while (true)
{
Thread.Sleep(500);
cts.Token.ThrowIfCancellationRequested();
Console.WriteLine("i = {0}", i);
i++;
if (i > 3) throw new InvalidOperationException();
}
}
You get the expected Canceled result, if you pass the token to Task.Run:
var task = Task.Run(() =>
{
try
{
Tester(cts);
}
catch (Exception e)
{
Console.WriteLine("Exception {0}", e.Message);
throw;
}
}, cts.Token);
The task needs to know which token will throw the OperationCanceledException - only an exception from the correct source cancels the task, every other exception will just fault it.
From MSDN:
When a task instance observes an OperationCanceledException thrown by user code, it compares the exception's token to its associated token (the one that was passed to the API that created the Task). If they are the same and the token's IsCancellationRequested property returns true, the task interprets this as acknowledging cancellation and transitions to the Canceled state.
I don't fully understand why you get the Canceled result in the first place, it looks like it has to do with when the CancellationTokenSource is captured.
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;
}
}
}
}