I am trying to set the continuation task after it got cancelled. But I am not able to handle the exception internally.
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
throw new OperationCanceledException();
}, token).ContinueWith((t) =>
{
t.Exception.Handle((e) => true);
Console.WriteLine("You have cancelled the task");
}, TaskContinuationOptions.OnlyOnCanceled);
Console.WriteLine("Press enter to stop the task");
Console.ReadLine();
cancellationTokenSource.Cancel();
task.Wait();
Console.WriteLine("Press enter to end the application");
Console.ReadLine();
The exception is not handled by the code. If i wrap the code with a try block I get an error saying "Object instance not set to the reference of an object".
Any idea?
Related
I am trying to stop a task in C# after a certain period of time.
For my code: the Task.Delay().Wait() should just represent some work that the Task does.
My Code:
public static void Main()
{
Console.WriteLine("starting app");
try
{
Console.WriteLine("before");
DoStuff(1000);
Console.WriteLine("after");
}
catch
{
Console.WriteLine("TIMEOUT");
}
Console.WriteLine("Main finished wait 5 sec now");
Task.Delay(10000).Wait();
Console.WriteLine("Closing app now");
}
public static async Task DoStuff(int time)
{
Task real = Task.Run(()=>
{
Task.Delay(2000).Wait();
Console.WriteLine("Task Running");
Task.Delay(2000).Wait();
Console.WriteLine("still running");
Task.Delay(2000).Wait();
Console.WriteLine("Not dead yet");
Task.Delay(1000).Wait();
Console.WriteLine("Task done");
Task.Delay(5000);
});
bool success = real.Wait(time);
if ( success )
{
Console.WriteLine("Task finished in time");
await real;
}
else
{
Console.WriteLine("Task did not finish");
real.Dispose();
throw new TimeoutException("The task took too long");
}
}
I already tried it with the cancellation token, but I do not have a loop to check the token every iteration. So I tried to do it with the Task.Wait(time) and I get the right message, but the task does not stop after using the Task.Dispose() method. I get the following output:
So I get the current output but the task continues to run in the back.. Any ideas on how to solve this?
Firstly: never Wait() on tasks (unless you know they have already finished); use await. As for the timeout: CancellationTokenSource can be made to time out after a delay, as below.
Note that cancellation-tokens do not interrupt code - you (or other code, as in the case of Task.Delay) need to either check for cancellation (for example, ThrowIfCancellationRequested()), or you need to use Register(...) to add a callback that will be invoked when it is cancelled.
using System;
using System.Threading;
using System.Threading.Tasks;
static class P
{
public static async Task Main()
{
Console.WriteLine("starting app");
try
{
Console.WriteLine("before");
await DoStuffAsync(1000);
Console.WriteLine("after");
}
catch
{
Console.WriteLine("TIMEOUT");
}
Console.WriteLine("Main finished wait 5 sec now");
await Task.Delay(5000);
Console.WriteLine("Closing app now");
}
public static async Task DoStuffAsync(int time)
{
using var cts = new CancellationTokenSource(time); // assuming here that "time" is milliseconds
Task real = Task.Run(async () =>
{
await Task.Delay(2000, cts.Token);
Console.WriteLine("Task Running");
await Task.Delay(2000, cts.Token);
Console.WriteLine("still running");
await Task.Delay(2000, cts.Token);
Console.WriteLine("Not dead yet");
await Task.Delay(2000, cts.Token);
Console.WriteLine("Task done");
await Task.Delay(2000, cts.Token);
});
bool success;
try
{
await real;
success = true;
}
catch (OperationCanceledException)
{
success = false;
}
if (success)
{
Console.WriteLine("Task finished in time");
}
else
{
Console.WriteLine("Task tool too long");
}
}
}
Note that you can also pass a cancellation-token into Task.Run, but that simply gets checked before executing the callback - and there isn't usually a significant delay on that, so... it isn't usually worth it.
Additional note: if you want to be sure exactly what was cancelled (i.e. is the OperationCanceledException coming from cts):
catch (OperationCanceledException cancel) when (cancel.CancellationToken == cts.Token)
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 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.
I have the following code, where a Task can be canceled, but I basically need to wait for it to complete (to ensure integrity) before throwing the OperationCanceledException to the caller.
public static void TaskCancellationTest() {
try {
Console.WriteLine("TaskCancellationTest started.");
var cts = new CancellationTokenSource();
var t = Task.Run(() => {
if (cts.Token.IsCancellationRequested) return;
Console.WriteLine("1");
Task.Delay(2000).Wait();
Console.WriteLine("2");
}).ContinueWith(task => {
if (cts.Token.IsCancellationRequested) return;
Console.WriteLine("3");
Task.Delay(2000).Wait();
Console.WriteLine("4");
});
Task.Run(() => {
Task.Delay(1000).Wait();
Console.WriteLine("Cancelling...");
cts.Cancel();
});
t.Wait();
try {
cts.Token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException) {
Console.WriteLine("Gracefully canceled.");
}
Console.WriteLine("TaskCancellationTest completed.");
}
catch (Exception ex) {
Console.WriteLine("TaskCancellationTest... Failure: " + ex);
}
}
The result, as expected, is:
1
Cancelling...
2
Gracefully canceled.
It works, but I would prefer to pass the CancellationToken to the methods as I understand this is a better pattern. I would also like to be able to observe the token inside the method body and to call ThrowIfCancellationRequested() to abort without having to wait for the next ContinueWith().
I was playing with the following alternative code, which also works, but is there any way to have an OperationCanceledException raised instead of an AggregateException?
If I pass the cancellationToken to the WaitAll() method, the problem is that it will throw an OperationCanceledException immediately upon cancellation of the token, rather than waiting for the tasks t1 and t2 to actually complete (they will continue running in the background) and then only throwing the exception.
public static void TaskCancellationTest2() {
try {
Console.WriteLine("TaskCancellationTest2 started.");
var cts = new CancellationTokenSource();
var t1 = Task.Run(() => {
Console.WriteLine("1");
Task.Delay(2000).Wait();
Console.WriteLine("2");
}, cts.Token);
var t2 = t1.ContinueWith(task => {
Console.WriteLine("3");
Task.Delay(2000).Wait();
cts.Token.ThrowIfCancellationRequested();
Console.WriteLine("4");
}, cts.Token);
Task.Run(() => {
Task.Delay(1000).Wait();
Console.WriteLine("Cancelling...");
cts.Cancel();
});
try {
try {
Task.WaitAll(t1, t2);
}
catch (AggregateException ae) {
if (ae.InnerExceptions.Count == 1 && ae.InnerExceptions.Single() is OperationCanceledException) {
throw ae.InnerExceptions.Single();
}
throw;
}
}
catch (OperationCanceledException) {
Console.WriteLine("Gracefully canceled.");
}
Console.WriteLine("TaskCancellationTest2 completed.");
}
catch (Exception ex) {
Console.WriteLine("TaskCancellationTest2... Failure: " + ex);
}
}
I have prepared a fiddle here.
This question's title is very similar to mine, but the accepted answer is unfortunately not relevant to my case.
Do you know of any way to achieve what I would like, that makes as good use of CancellationToken as possible?
I think the TPL is designed to eagerly complete tasks if the CancellationToken is set. Part of the reason you are seeing this behavior is because you are calling t.Wait(cts.Token). The overload that takes a CancellationToken will stop waiting if the token is set even if the task hasn't ran to completion.
It's the same with ContinueWith if you pass in a CancellationToken the task can complete as soon as that token is set.
Change your code to call t.Wait() and ContinueWith without a token and you'll get the behavior you want.
public static void TaskCancellationTestNotWorking1()
{
try
{
Console.WriteLine("TaskCancellationTestNotWorking started.");
var cts = new CancellationTokenSource();
var t = Task.Run(() =>
{
Console.WriteLine("1");
Thread.Sleep(2000);
Console.WriteLine("2");
}, cts.Token).ContinueWith(task =>
{
Console.WriteLine("3");
Thread.Sleep(2000);
cts.Token.ThrowIfCancellationRequested();
Console.WriteLine("4");
});
Task.Run(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Cancelling...");
cts.Cancel();
}, cts.Token);
try
{
t.Wait();
}
catch (OperationCanceledException)
{
Console.WriteLine("IsCanceled " + t.IsCanceled);
Console.WriteLine("IsCompleted " + t.IsCompleted);
Console.WriteLine("Gracefully canceled.");
}
catch (AggregateException)
{
Console.WriteLine("IsCanceled " + t.IsCanceled);
Console.WriteLine("IsCompleted " + t.IsCompleted);
Console.WriteLine("Gracefully canceled 1.");
}
Console.WriteLine("TaskCancellationTestNotWorking completed.");
}
catch (Exception ex)
{
Console.WriteLine("TaskCancellationTestNotWorking... Failure: " + ex);
}
}
You might find this article useful How do I cancel non-cancelable async operations?
I have a cancellation token like so
static CancellationTokenSource TokenSource= new CancellationTokenSource();
I have a blocking collection like so
BlockingCollection<object> items= new BlockingCollection<object>();
var item = items.Take(TokenSource.Token);
if(TokenSource.CancelPending)
return;
When I call
TokenSource.Cancel();
The Take does not continue like it should. If I use the TryTake with a poll the Token shows it is being set as Canceled.
That's working as expected. If the operation is canceled, items.Take will throw OperationCanceledException. This code illustrates it:
static void DoIt()
{
BlockingCollection<int> items = new BlockingCollection<int>();
CancellationTokenSource src = new CancellationTokenSource();
ThreadPool.QueueUserWorkItem((s) =>
{
Console.WriteLine("Thread started. Waiting for item or cancel.");
try
{
var x = items.Take(src.Token);
Console.WriteLine("Take operation successful.");
}
catch (OperationCanceledException)
{
Console.WriteLine("Take operation was canceled. IsCancellationRequested={0}", src.IsCancellationRequested);
}
});
Console.WriteLine("Press ENTER to cancel wait.");
Console.ReadLine();
src.Cancel(false);
Console.WriteLine("Cancel sent. Press Enter when done.");
Console.ReadLine();
}