C# Task Run Cancellation Token cleanup after cancellation - c#

How do I do some cleanup after checking if the cancellation token has been canceled but what I've found is if I try to do anything it the below code, everything works as expected except the cancellation, as soon as await LeaveGroup(SIGNALR_NETWORK_TEST_GROUP); is called the thread ends and nothing after it happens I've tried several different calls and it doesn't matter it just ends immediately instead of waiting for me to clean things up before I return.
What am I missing here? I even found a couple of examples from Microsoft that lead me to believe I'm on the right track but it doesn't let me execute anything else after I've checked if it's been canceled.
https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-cancellation
https://learn.microsoft.com/en-us/dotnet/standard/threading/how-to-listen-for-cancellation-requests-by-polling
static void Main()
{
try
{
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var signalRClient = new SignalRClient();
Task.Run(async () => await signalRClient.Start(cancellationToken), cancellationToken);
Console.ReadKey();
cancellationTokenSource.Cancel();
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
public async Task Start(CancellationToken cancellationToken)
{
var signalRConnectionInfo = await GetConnectionInfo();
hubConnection = new HubConnectionBuilder()
.WithUrl(signalRConnectionInfo.Url, options =>
{
options.AccessTokenProvider = () => Task.FromResult(signalRConnectionInfo.AccessToken);
})
.WithAutomaticReconnect()
.Build();
hubConnection.On<string>("OnUpdate", param =>
{
Console.WriteLine(param);
});
await hubConnection.StartAsync();
if (hubConnection.State == HubConnectionState.Connected)
await JoinGroup(SIGNALR_NETWORK_TEST_GROUP);
while (hubConnection.State == HubConnectionState.Connected)
{
if (cancellationToken.IsCancellationRequested)
{
Debug.WriteLine("Cancellation Requested!");
await LeaveGroup(SIGNALR_NETWORK_TEST_GROUP);
throw new OperationCanceledException("SignalR connection cancelled!", cancellationToken);
}
}

Related

How to correctly wait for callback with timeout fallback

I am testing web socket subscriptions in my tests and I would like to wait for response from callback and then end the test, if no response is received after timeout end the test.
This is what I have now (simplified) but I am not sure if its the way how to do it.
public async Task WaitForPing()
{
var cancellationTokenSource = new CancellationTokenSource(5_000);
var pinged = false;
using var _ = Client.OnPing(_ =>
{
pinged = true;
cancellationTokenSource.Cancel();
}
await Client.Run();
await Task
.Delay(-1, cancellationTokenSource.Token)
.ContinueWith(_ => { }, CancellationToken.None);
Assert(pinged);
}
A proper method should be like that:
static Task<string> WaitForResponseAsync(CancellationTokenSource cancellationTokenSource = default)
{
var tcs = new TaskCompletionSource<string>();
// Register a method that throws an exception when task cancelled.
cancellationTokenSource.Token.Register(()=> throw new Exception("Timed out!"));
// Replace this task with your async operation. Like OnPing(_ => ...
Task.Run(async () =>
{
await Task.Delay(30_000); // Response will be received after 30 seconds
tcs.SetResult("Hello World");
});
return tcs.Task; // Return awaitable task
}
And place where you call that method:
try
{
Console.WriteLine(await WaitForResponseAsync(new CancellationTokenSource(5_000))); // Time out is 5 seconds
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Don't worry about the task in a task, you should replace it with your event. So if I customize it according to your method, it should be something like that:
Task WaitForPing(CancellationTokenSource cancellationTokenSource)
{
var tcs = new TaskCompletionSource();
cancellationTokenSource.Token.Register(() => throw new Exception("Timed out"));
// Or just call SetResult to finish the task before completed without exception:
// cancellationTokenSource.Token.Register(() => tcs.SetResult());
// (Personally I do not recommend this one)
using var _ = Client.OnPing(_ =>
{
tcs.SetResult();
};
return tcs.Task;
}

how to cancel Task from another Task?

try{
var cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
Task.Run(()=>
{
//DoSomething(); excute long time
}, ct);
Task.Run(()=>
{
Thread.Sleep(1000);
cts.Cancel();
}, ct).Wait();
}
catch (OperationCanceledException ex)
{
Console.WriteLine("exception" + ex.Message);
}
finally
{
Console.WriteLine("finally");
}
When I call cts.Cancel()
DoSomething still Work.....................
how can i stop first Task?
if DoSomething Has a loop
I can add ct.ThrowIfCancellationRequested() , it's working
but DoSomething not a loop , what can i do ?
Whether or not DoSomething() is a loop, it must explicitly check and respond to the IsCancellationRequested property on the cancellation Token. If this property is true, the function must return as soon as practical, even if it means not completing the full execution. Note that DoSomething() is not a loop.
void DoSomething(System.Threading.CancellationToken tok)
{
Thread.Sleep(900);
if (tok.IsCancellationRequested)
return;
Console.WriteLine("after 1");
Thread.Sleep(1800);
if (tok.IsCancellationRequested)
return;
Console.WriteLine("after 2");
}
void Main()
{
try
{
var cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
System.Threading.Tasks.Task.Run(() =>
{
DoSomething(ct);
//DoSomething(); excute long time
});
System.Threading.Tasks.Task.Run(() =>
{
Thread.Sleep(1000);
cts.Cancel();
}).Wait();
}
catch (OperationCanceledException ex)
{
Console.WriteLine("exception" + ex.Message);
}
finally
{
Console.WriteLine("finally");
}
}
Note: DoSomething() must reference the cancellation token and explicitly check the IsCancellationRequested property. The role of the cancellation token in the Task.Run() is explained in this answer: https://stackoverflow.com/a/3713113/41410, but it doesn't play a role is cancelling the flow of DoSomething()

when Task.IsCancelled is set as true?

when will come Task.IsCanceled = true;
Code:
var cts = new CancellationTokenSource();
string result = "";
cts.CancelAfter(10000);
try
{
Task t = Task.Run(() =>
{
using (var stream = new WebClient().OpenRead("http://www.rediffmail.com"))
{
result = "success!";
}
cts.Token.ThrowIfCancellationRequested();
}, cts.Token);
Stopwatch timer = new Stopwatch();
timer.Start();
while (timer.IsRunning)
{
if (timer.ElapsedMilliseconds <= 10000)
{
if (result != ""){
timer.Stop();
Console.WriteLine(result);
}
}
else
{
timer.Stop();
//cts.Cancel();
//cts.Token.ThrowIfCancellationRequested();
}
}
}
catch (OperationCanceledException)
{
Console.WriteLine(t.IsCanceled); // still its appear in false.
}
My requirement is - Task is not completed upto 10seconds, Need to cancel the task.
So I am setting timer and watch upto the given seconds. its not completed mean cancel the task and showing error message.
You have to pass the token to your method. It should inspect the token and respect the call to Cancel() of the CancellationTokenSource.
Or you do it yourself:
Task t = Task.Factory.StartNew(() =>
{
myResult = method(); // Request processing in parallel
cts.Token.ThrowIfCancellationRequested(); // React on cancellation
}, cts.Token);
A complete example is this:
async Task Main()
{
var cts = new CancellationTokenSource();
var ct = cts.Token;
cts.CancelAfter(500);
Task t = null;
try
{
t = Task.Run(() => { Thread.Sleep(1000); ct.ThrowIfCancellationRequested(); }, ct);
await t;
Console.WriteLine(t.IsCanceled);
}
catch (OperationCanceledException)
{
Console.WriteLine(t.IsCanceled);
}
}
The output is that an OperationCanceledException is thrown and the result is
True
if you remove the ct.ThrowIfCancellationRequested(); part it will show
False
Edit:
Now, you have updated the question, some comments on that. First, you won't need the timer anymore since you are using the CancelAfter method. Second, you need to await your task. So that makes something like this:
string result = "";
cts.CancelAfter(10000);
Task t = null;
try
{
t = Task.Run(() =>
{
using (var stream = new WebClient().OpenRead("http://www.rediffmail.com"))
{
cts.Token.ThrowIfCancellationRequested();
result = "success!";
}
}, cts.Token);
await t;
}
catch (OperationCanceledException)
{
Console.WriteLine(t.IsCanceled);
}
This should show that t.IsCanceled is true but of course only when the call of the WebClient takes longer that 10 seconds.
From documentation:
A Task will complete in the TaskStatus.Canceled state under any of the following conditions:
Its CancellationToken was marked for cancellation before the task started executing.
The task acknowledged the cancellation request on its already signaled CancellationToken by throwing an OperationCanceledException that bears the same CancellationToken.
The task acknowledged the cancellation request on its already signaled CancellationToken by calling the ThrowIfCancellationRequested method on the CancellationToken.
So basically you would need to throw an OperationCanceledException within your task to force the state for instance by executing cts.Token.ThrowIfCancellationRequested() just after you cancel it.
But the intention of this mechanism is a bit the other way around. You cancel source say while user presses cancel button on your form (from outside of your task) an task just verifies if cancellation was requested in some safe to cancel points of its code.

Stop execution if one of the tasks is faulted

I have a question on TPL tasks.
I have some tasks that are a "Show Stoppers", once one of them is faulted i dont want the method to continue running but give an exception and exit.
I tried using TaskContinuationOptions, something like this:
var res = Task.Factory.ContinueWhenAny(
new[] { task1, task2, task3},
task =>
{
throw task.Exception.Flatten();
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted,
this.taskScheduler);
var res1 = Task.Factory.ContinueWhenAll(
new[] { task1, task2, task3},
tasks =>
{
// DO SOME CODE
},
CancellationToken.None,
TaskContinuationOptions.NotOnFaulted,
this.taskScheduler);
return Task.WhenAny(res, res1).Unwrap();
But unfortunately there is a limitation filtering on a TaskContinuationOptions when continuing on more that a one task.
What is the solution to this?
You can implement a loop which checks if the tasks are faulted as they finish. If one of them faults, you could throw and exit the method:
List<Task> tasks = new List<Task> {task1, task2, task3}; // Show stopping tasks.
while (tasks.Count > 0)
{
var finishedTask = await Task.WhenAny(tasks);
tasks.Remove(finishedTask);
if (finishedTask.Status == TaskStatus.Faulted)
{
// Throw and exit the method.
}
}
// Continuation code goes here.
Note this will not cancel the other ongoing tasks. You could implement a cancelling mechanism if needed using CancellationToken and explicitly cancel the remaining tasks. Your tasks will need to monitor the cancellation token to see if there was a request for cancellation, either by looking at CancellationToken.IsCancellationRequested property or by using the CancellationToken.ThrowIfCancellationRequested method:
var cts = new CancellationTokenSource();
// Generate some tasks for this example.
var task1 = Task.Run(async () => await Task.Delay(1000, cts.Token), cts.Token);
var task2 = Task.Run(async () => await Task.Delay(2000, cts.Token), cts.Token);
var task3 = Task.Run(async () => await Task.Delay(3000, cts.Token), cts.Token);
List<Task> tasks = new List<Task> {task1, task2, task3};
while (tasks.Count > 0)
{
var finishedTask = await Task.WhenAny(tasks);
tasks.Remove(finishedTask);
if (finishedTask.Status == TaskStatus.Faulted)
{
cts.Cancel();
// Throw and exit the method.
}
}
You can use a CancellationTokenSource and run all your tasks with this cancellation token , so if the token is cancelled then rest of tasks will be cancelled as well
var cs = new CancellationTokenSource();
var options = new ParallelOptions { CancellationToken = cs.Token };
var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
try
{
Parallel.ForEach(array, options, (index) =>
{
CallCustomMethod(cs, index);
});
}
catch (OperationCanceledException ex)
{
}
void CallCustomMethod(CancellationTokenSource cs, int index)
{
try
{
if (cs.IsCancellationRequested)
{
return;
}
if (index == 4)
{
throw new Exception("Cancel");
}
Console.WriteLine(index);
}
catch
{
cs.Cancel();
}
}

Correct way to delay the start of a Task

I want to schedule a task to start in x ms and be able to cancel it before it starts (or just at the beginning of the task).
The first attempt would be something like
var _cancelationTokenSource = new CancellationTokenSource();
var token = _cancelationTokenSource.Token;
Task.Factory.StartNew(() =>
{
token.ThrowIfCancellationRequested();
Thread.Sleep(100);
token.ThrowIfCancellationRequested();
}).ContinueWith(t =>
{
token.ThrowIfCancellationRequested();
DoWork();
token.ThrowIfCancellationRequested();
}, token);
But I feel like there should be a better way, as this would use up a thread while in the sleep, during which it could be canceled.
What are my other options?
Like Damien_The_Unbeliever mentioned, the Async CTP includes Task.Delay. Fortunately, we have Reflector:
public static class TaskEx
{
static readonly Task _sPreCompletedTask = GetCompletedTask();
static readonly Task _sPreCanceledTask = GetPreCanceledTask();
public static Task Delay(int dueTimeMs, CancellationToken cancellationToken)
{
if (dueTimeMs < -1)
throw new ArgumentOutOfRangeException("dueTimeMs", "Invalid due time");
if (cancellationToken.IsCancellationRequested)
return _sPreCanceledTask;
if (dueTimeMs == 0)
return _sPreCompletedTask;
var tcs = new TaskCompletionSource<object>();
var ctr = new CancellationTokenRegistration();
var timer = new Timer(delegate(object self)
{
ctr.Dispose();
((Timer)self).Dispose();
tcs.TrySetResult(null);
});
if (cancellationToken.CanBeCanceled)
ctr = cancellationToken.Register(delegate
{
timer.Dispose();
tcs.TrySetCanceled();
});
timer.Change(dueTimeMs, -1);
return tcs.Task;
}
private static Task GetPreCanceledTask()
{
var source = new TaskCompletionSource<object>();
source.TrySetCanceled();
return source.Task;
}
private static Task GetCompletedTask()
{
var source = new TaskCompletionSource<object>();
source.TrySetResult(null);
return source.Task;
}
}
Since .NET 4.5 has now been released, there's a very simple built-in way to delay a task: just use Task.Delay(). behind the scenes, it uses the implementation that ohadsc decompiled.
The correct answer in the future will probably be Task.Delay. However, that's currently only available through the Async CTP (and in the CTP, it's on TaskEx rather than Task).
Unfortunately, because it's only in CTP, there aren't many good links to documentation for it either.
Look at the TaskFactoryExtensions_Delayed in "Parallel Programming with .NET 4 Samples".
I haven't tested this, but here is a first-pass at wrapper methods to create an initial 'Delay' Task or to continue after a Delay. If you find issues, feel free to correct.
public static Task StartDelayTask(int delay, CancellationToken token)
{
var source = new TaskCompletionSource<Object>();
Timer timer = null;
timer = new Timer(s =>
{
source.TrySetResult(null);
timer.Dispose();
}, null, delay, -1);
token.Register(() => source.TrySetCanceled());
return source.Task;
}
public static Task ContinueAfterDelay
(this Task task,
int delay, Action<Task> continuation,
CancellationToken token)
{
var source = new TaskCompletionSource<Object>();
Timer timer = null;
var startTimer = new Action<Task>(t =>
{
timer = new Timer(s =>
{
source.TrySetResult(null);
timer.Dispose();
},null,delay,-1);
});
task.ContinueWith
(startTimer,
token,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Current);
token.Register(() => source.TrySetCanceled());
return source.Task.ContinueWith(continuation, token);
}
You can use Token.WaitHandle.WaitOne(int32 milliseconds) overload method to specify number of milliseconds to wait for your task. But key difference between Thread.Sleep(xxx) and Token.WaitHandle.WaitOne(xxx) that later blocks thread until the time specified elapsed or the token has been canceled.
Here is an example
void Main()
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var task = Task.Factory.StartNew(() =>
{
// wait for 5 seconds or user hit Enter key cancel the task
token.WaitHandle.WaitOne(5000);
token.ThrowIfCancellationRequested();
Console.WriteLine("Task started its work");
});
Console.WriteLine("Press 'Enter' key to cancel your task");
Console.Read();
tokenSource.Cancel();
}

Categories