Synchron Task Cancellation - c#

I am looking for a way to do a synchronous task cancellation. I have a task like this:
public Task AddAsync(CancellationToken cancellationToken)
{
Task result = Task.Factory.StartNew(() =>
{
// So wait forever
var wait = new ManualResetEvent(false);
var waitIndex = WaitHandle.WaitAny(new[] { wait,
cancellationToken.WaitHandle});
switch (waitIndex)
{
case 0:
Debug.WriteLine("Everything fine");
break;
case 1:
Debug.WriteLine("Cancelled");
Thread.Sleep(5000);
cancellationToken.ThrowIfCancellationRequested();
break;
}
return;
}, cancellationToken);
return result;
}
And the asynchronous method will be called like this:
var ts = new CancellationTokenSource();
var t = AddAsync(ts.Token);
if (!t.Wait(TimeSpan.FromSeconds(5)))
{
ts.Cancel();
}
So after the timeout I call cancel but the task is taking some time to cancel. So how can I make a synchronous cancel call?

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 restart a async method? Cancel previous run, await it and then start it

I have a method RestartAsync which starts a method DoSomethingAsync. When RestartAsync is called again it should cancel DoSomethingAsyncand await until it is finished (DoSomethingAsync can NOT be cancelled synchronously and it should NOT be called when a previous task is still in progress).
My first approach looked like this:
public async Task RestartTest()
{
Task[] allTasks = { RestartAsync(), RestartAsync(), RestartAsync() } ;
await Task.WhenAll(allTasks);
}
private async Task RestartAsync()
{
_cts.Cancel();
_cts = new CancellationTokenSource();
await _somethingIsRunningTask;
_somethingIsRunningTask = DoSomethingAsync(_cts.Token);
await _somethingIsRunningTask;
}
private static int _numberOfStarts;
private async Task DoSomethingAsync(CancellationToken cancellationToken)
{
_numberOfStarts++;
int numberOfStarts = _numberOfStarts;
try
{
Console.WriteLine(numberOfStarts + " Start to do something...");
await Task.Delay(TimeSpan.FromSeconds(1)); // This operation can not be cancelled.
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
Console.WriteLine(numberOfStarts + " Finished to do something...");
}
catch (OperationCanceledException)
{
Console.WriteLine(numberOfStarts + " Cancelled to do something...");
}
}
The actual output when calling RestartAsync three times looks like this (Note that the second run is cancelling and awaiting the first, but at the same time the third run is also awaiting the first instead of cancelling and awaiting the second one):
1 Start to do something...
1 Cancelled to do something...
2 Start to do something...
3 Start to do something...
2 Finished to do something...
3 Finished to do something...
But what I want to achieve is this output:
1 Start to do something...
1 Cancelled to do something...
2 Start to do something...
2 Cancelled to do something...
3 Start to do something...
3 Finished to do something...
My current solution is the following:
private async Task RestartAsync()
{
if (_isRestarting)
{
return;
}
_cts.Cancel();
_cts = new CancellationTokenSource();
_isRestarting = true;
await _somethingIsRunningTask;
_isRestarting = false;
_somethingIsRunningTask = DoSomethingAsync(_cts.Token);
await _somethingIsRunningTask;
}
Then I get this output:
1 Start to do something...
1 Cancelled to do something...
2 Start to do something...
2 Finished to do something...
Now at least DoSomethingAsync is not started while it is still in progress (Note that third run is ignored, which does not really matter, because it should cancel the second run otherwise).
But this solution doesn't feel good and I have to repeat this ugly pattern wherever I want this kind of behavior. Is there any good pattern or framework for this kind of restart mechanic?
I think the problem is inside RestartAsync method. Beware that an async method will immediately return a task if it's going to await something, so second RestartAsync actually return before it swap its task then third RestartAsync comes in and awaiting the task first RestartAsync.
Also if RestartAsync is going to be executed by multiple thread, you may want to wrap _cts and _somethingIsRunningTask into one and swap values with Interlocked.Exchange method to prevent race condition.
Here is my example code, not fully tested:
public class Program
{
static async Task Main(string[] args)
{
RestartTaskDemo restartTaskDemo = new RestartTaskDemo();
Task[] tasks = { restartTaskDemo.RestartAsync( 1000 ), restartTaskDemo.RestartAsync( 1000 ), restartTaskDemo.RestartAsync( 1000 ) };
await Task.WhenAll( tasks );
Console.ReadLine();
}
}
public class RestartTaskDemo
{
private int Counter = 0;
private TaskEntry PreviousTask = new TaskEntry( Task.CompletedTask, new CancellationTokenSource() );
public async Task RestartAsync( int delay )
{
TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
TaskEntry previousTaskEntry = Interlocked.Exchange( ref PreviousTask, new TaskEntry( taskCompletionSource.Task, cancellationTokenSource ) );
previousTaskEntry.CancellationTokenSource.Cancel();
await previousTaskEntry.Task.ContinueWith( Continue );
async Task Continue( Task previousTask )
{
try
{
await DoworkAsync( delay, cancellationTokenSource.Token );
taskCompletionSource.TrySetResult( true );
}
catch( TaskCanceledException )
{
taskCompletionSource.TrySetCanceled();
}
}
}
private async Task DoworkAsync( int delay, CancellationToken cancellationToken )
{
int count = Interlocked.Increment( ref Counter );
Console.WriteLine( $"Task {count} started." );
try
{
await Task.Delay( delay, cancellationToken );
Console.WriteLine( $"Task {count} finished." );
}
catch( TaskCanceledException )
{
Console.WriteLine( $"Task {count} cancelled." );
throw;
}
}
private class TaskEntry
{
public Task Task { get; }
public CancellationTokenSource CancellationTokenSource { get; }
public TaskEntry( Task task, CancellationTokenSource cancellationTokenSource )
{
Task = task;
CancellationTokenSource = cancellationTokenSource;
}
}
}
This is a concurrency problem. So, you'll need a solution for concurrency problems: a semaphore.
In the generic case, you should account also for when the method being runs throws an OperationCanceledException:
private async Task DoSomethingAsync(CancellationToken cancellationToken)
{
_numberOfStarts++;
int numberOfStarts = _numberOfStarts;
try
{
Console.WriteLine(numberOfStarts + " Start to do something...");
await Task.Delay(TimeSpan.FromSeconds(1)); // This operation can not be cancelled.
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
Console.WriteLine(numberOfStarts + " Finished to do something...");
}
catch (OperationCanceledException)
{
Console.WriteLine(numberOfStarts + " Cancelled to do something...");
throw;
}
}
Try this:
private SemaphoreSlim semaphore = new SemaphoreSlim(1);
private (CancellationTokenSource cts, Task task)? state;
private async Task RestartAsync()
{
Task task = null;
await this.semaphore.WaitAsync();
try
{
if (this.state.HasValue)
{
this.state.Value.cts.Cancel();
this.state.Value.cts.Dispose();
try
{
await this.state.Value.task;
}
catch (OperationCanceledException)
{
}
this.state = null;
}
var cts = new CancellationTokenSource();
task = DoSomethingAsync(cts.Token);
this.state = (cts, task);
}
finally
{
this.semaphore.Release();
}
try
{
await task;
}
catch (OperationCanceledException)
{
}
}

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.

How to cancel a Task in await?

I'm playing with these Windows 8 WinRT tasks, and I'm trying to cancel a task using the method below, and it works to some point. The CancelNotification method DOES get called, which makes you think the task was cancelled, but in the background the task keeps running, then after it's completed, the status of the Task is always completed and never cancelled. Is there a way to completely halt the task when it's cancelled?
private async void TryTask()
{
CancellationTokenSource source = new CancellationTokenSource();
source.Token.Register(CancelNotification);
source.CancelAfter(TimeSpan.FromSeconds(1));
var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token);
await task;
if (task.IsCompleted)
{
MessageDialog md = new MessageDialog(task.Result.ToString());
await md.ShowAsync();
}
else
{
MessageDialog md = new MessageDialog("Uncompleted");
await md.ShowAsync();
}
}
private int slowFunc(int a, int b)
{
string someString = string.Empty;
for (int i = 0; i < 200000; i++)
{
someString += "a";
}
return a + b;
}
private void CancelNotification()
{
}
Read up on Cancellation (which was introduced in .NET 4.0 and is largely unchanged since then) and the Task-Based Asynchronous Pattern, which provides guidelines on how to use CancellationToken with async methods.
To summarize, you pass a CancellationToken into each method that supports cancellation, and that method must check it periodically.
private async Task TryTask()
{
CancellationTokenSource source = new CancellationTokenSource();
source.CancelAfter(TimeSpan.FromSeconds(1));
Task<int> task = Task.Run(() => slowFunc(1, 2, source.Token), source.Token);
// (A canceled task will raise an exception when awaited).
await task;
}
private int slowFunc(int a, int b, CancellationToken cancellationToken)
{
string someString = string.Empty;
for (int i = 0; i < 200000; i++)
{
someString += "a";
if (i % 1000 == 0)
cancellationToken.ThrowIfCancellationRequested();
}
return a + b;
}
Or, in order to avoid modifying slowFunc (say you don't have access to the source code for instance):
var source = new CancellationTokenSource(); //original code
source.Token.Register(CancelNotification); //original code
source.CancelAfter(TimeSpan.FromSeconds(1)); //original code
var completionSource = new TaskCompletionSource<object>(); //New code
source.Token.Register(() => completionSource.TrySetCanceled()); //New code
var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token); //original code
//original code: await task;
await Task.WhenAny(task, completionSource.Task); //New code
You can also use nice extension methods from https://github.com/StephenCleary/AsyncEx and have it looks as simple as:
await Task.WhenAny(task, source.Token.AsTask());
One case which hasn't been covered is how to handle cancellation inside of an async method. Take for example a simple case where you need to upload some data to a service get it to calculate something and then return some results.
public async Task<Results> ProcessDataAsync(MyData data)
{
var client = await GetClientAsync();
await client.UploadDataAsync(data);
await client.CalculateAsync();
return await client.GetResultsAsync();
}
If you want to support cancellation then the easiest way would be to pass in a token and check if it has been cancelled between each async method call (or using ContinueWith). If they are very long running calls though you could be waiting a while to cancel. I created a little helper method to instead fail as soon as canceled.
public static class TaskExtensions
{
public static async Task<T> WaitOrCancel<T>(this Task<T> task, CancellationToken token)
{
token.ThrowIfCancellationRequested();
await Task.WhenAny(task, token.WhenCanceled());
token.ThrowIfCancellationRequested();
return await task;
}
public static Task WhenCanceled(this CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
return tcs.Task;
}
}
So to use it then just add .WaitOrCancel(token) to any async call:
public async Task<Results> ProcessDataAsync(MyData data, CancellationToken token)
{
Client client;
try
{
client = await GetClientAsync().WaitOrCancel(token);
await client.UploadDataAsync(data).WaitOrCancel(token);
await client.CalculateAsync().WaitOrCancel(token);
return await client.GetResultsAsync().WaitOrCancel(token);
}
catch (OperationCanceledException)
{
if (client != null)
await client.CancelAsync();
throw;
}
}
Note that this will not stop the Task you were waiting for and it will continue running. You'll need to use a different mechanism to stop it, such as the CancelAsync call in the example, or better yet pass in the same CancellationToken to the Task so that it can handle the cancellation eventually. Trying to abort the thread isn't recommended.
I just want to add to the already accepted answer. I was stuck on this, but I was going a different route on handling the complete event. Rather than running await, I add a completed handler to the task.
Comments.AsAsyncAction().Completed += new AsyncActionCompletedHandler(CommentLoadComplete);
Where the event handler looks like this
private void CommentLoadComplete(IAsyncAction sender, AsyncStatus status )
{
if (status == AsyncStatus.Canceled)
{
return;
}
CommentsItemsControl.ItemsSource = Comments.Result;
CommentScrollViewer.ScrollToVerticalOffset(0);
CommentScrollViewer.Visibility = Visibility.Visible;
CommentProgressRing.Visibility = Visibility.Collapsed;
}
With this route, all the handling is already done for you, when the task is cancelled it just triggers the event handler and you can see if it was cancelled there.

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