How to cancel await Task.Delay()? - c#

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

Related

Catching exceptions immediately does not work with Task.WhenAll

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

cancelling tasks throws exception but task continues

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

Stop Tasks with token used in an array of tasks

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

C# Task.Delay blocks second task

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

Run 2 tasks in parallel and cancel the 2nd task if the first completes .NET

If have 2 methods I want to run in parallel on different threads, let's call them Task1 and Task2. If Task2 completes before Task1 then I want the results of both combined into a list. If Task1 completes and Task2 is still running, it should cancel Task2 to avoid further processing. WhenAll doesn't work because it will wait for both tasks. WhenAny doesn't work because it will return if either finish first.
This is the solution I came up with.
static void Main(string[] args)
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var rates = new List<string>();
try
{
var p44Task = Task.Factory.StartNew(() => GetP44Rates(token), token);
var mgRates = await GetMGRates();
rates.AddRange(mgRates);
if (p44Task.IsCompleted && p44Task.Result.IsCompleted)
{
rates.AddRange(p44Task.Result.Result);
}
else
{
// Cancel the p44 task
tokenSource.Cancel();
}
}
catch (Exception e)
{
tokenSource.Cancel();
Console.WriteLine(e);
}
foreach (var rate in rates)
{
Console.WriteLine(rate);
}
Console.ReadLine();
}
private static async Task<List<string>> GetMGRates(CancellationToken cancellationToken)
{
var rates = new List<string>();
for (var i = 0; i < 10; i++)
{
rates.Add($"MG: {i}");
Console.WriteLine($"MG inside {i}");
await Task.Delay(1000);
}
return rates;
}
private static async Task<List<string>> GetP44Rates(CancellationToken ct)
{
var rates = new List<string>();
for (var i = 0; i < 50; i++)
{
rates.Add($"P44: {i}");
Console.WriteLine($"P44: {i}");
await Task.Delay(1000);
if (ct.IsCancellationRequested)
{
Console.WriteLine("bye from p44.");
Console.WriteLine("\nPress Enter to quit.");
ct.ThrowIfCancellationRequested();
}
}
return rates;
}

Categories

Resources