I have started to explore TPL in .NET to implement a windows service which will run multiple independent methods in parallel with some delay after each successful execution, following is a rough console application I came up with after looking at various examples:
class Program
{
static void Main(string[] args)
{
CancellationTokenSource _ct1 = new CancellationTokenSource();
CancellationTokenSource _ct2 = new CancellationTokenSource();
int count = 0;
var task1 = new Task(async () =>
{
try
{
while (true)
{
DoWork();
await Task.Delay(2000, _ct1.Token);
count++;
if (count >= 5)
{
throw new NotImplementedException();
}
}
}
catch (TaskCanceledException ex)
{
//Log cancellation and do not continue execution
Console.WriteLine("DoWork cancelled: " + ex.Message);
}
catch (Exception ex)
{
//Log error and continue with execution
Console.WriteLine("Error occurred at DoWork");
}
}, _ct1.Token, TaskCreationOptions.LongRunning);
var task2 = new Task(async () =>
{
try
{
while (true)
{
DoWork2();
await Task.Delay(2000, _ct2.Token);
count++;
if (count >= 5)
{
_ct2.Cancel();
}
}
}
catch (TaskCanceledException ex)
{
//Log cancellation and do not continue execution
Console.WriteLine("DoWork2 cancelled: " + ex.Message);
}
catch (Exception ex)
{
//Log error and continue with execution
Console.WriteLine("Error occurred at DoWork");
}
}, _ct2.Token, TaskCreationOptions.LongRunning);
task1.Start();
task2.Start();
Console.ReadKey();
}
public static void DoWork()
{
Console.WriteLine("Doing something...");
}
public static void DoWork2()
{
Console.WriteLine("Doing something else...");
}
}
In the above code when any exception occurs during the task execution, I need to log the error and then continue with the task execution, right now the task stops executing if there is an exception. My questions:
How to handle exceptions properly so that the task execution doesn't stop?
When I add a debugger break point at DoWork() method, the DoWork2() doesn't run which mean the tasks are not running in parallel on separate threads and running on a single thread and blocking each other. How to make sure the Tasks are running independent of each other on separate threads?
PS: The above code is a simple console app just to understand the workings of TPL, so please ignore if there are obvious design problems.
I believe that the simplest chage you can do to make your task1 running after exception is as follows:
var task1 = new Task(async () =>
{
while (true)
{
try
{
DoWork();
await Task.Delay(2000, _ct1.Token);
count++;
if (count >= 5)
{
throw new NotImplementedException();
}
}
catch (TaskCanceledException ex)
{
//Log cancellation and do not continue execution
Console.WriteLine("DoWork cancelled: " + ex.Message);
break;
}
catch (Exception ex)
{
//Log error and continue with execution
Console.WriteLine("Error occurred at DoWork");
count = 0; // without this you'll have exception thrown on each of further iterations
}
}
}, _ct1.Token, TaskCreationOptions.LongRunning);
Meaning that try...catch is moved inside the loop.
Please also note #pull420 comment on while you don't see task2 running
Related
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()
How would you implement an unlimited retry mechanism in case internal logics fails
something like this, but here you have only one change
static void Main(string[] args)
{
ILog Log = LogManager.GetLogger(typeof(Program));
try
{
StartWorking(Log);
}
catch (Exception ex)
{
Log.Error("Main exited with error: {0}. Restarting app", ex);
Thread.Sleep(5000);
StartWorking(Log);
}
}
private static void StartWorking(ILog Log)
{
Foo t = new Foo();
t.ReadConfiguration();
t.Login();
t.StartWorking();
}
You could use a while loop:
while (true)
{
try
{
StartWorking(Log);
// No exception was thrown, exit the loop
break;
}
catch (Exception ex)
{
Log.Error("Main exited with error: {0}. Restarting app", ex);
Thread.Sleep(5000);
}
}
Note however that this is very bad practice. You definitely don't want to be doing this. Instead you should have a retry logic that after a number of retries just gives up. For example:
const int maxRetries = 5;
for (var i = 0; i < maxRetries; i++)
{
try
{
StartWorking(Log);
// No exception was thrown, exit the loop
break;
}
catch (Exception ex)
{
Log.Error("Main exited with error: {0}. Restarting app", ex);
Thread.Sleep(5000);
}
if (i == maxRetries - 1)
{
throw new Exception("Sorry, we have reached the maximum number of retries for this operation, just giving up");
}
}
I'm trying to catch an exception thrown by a method (GetMoreCodes) run by a Task, but when debugging the exception is never handled and the catch block is never hit. Tried different techniques (in particular with/without await). This code is in an (async) button event handler.
try
{
// Task.Run(() => GetMoreCodes(CodeBufferMaxSize));
// await Task.Run(() => GetMoreCodes(0));
await Task.Run(() => { throw new Exception("test!"); });
}
catch (Exception ex)
{
Console.WriteLine("Exception caught: " + ex);
}
This looks to me like most examples I saw here and in blogs, in particular in this one (http://blog.stephencleary.com/ - big thanks #stephen-cleary).
For now, I only want the application not to crash and log an error if any.
Am I missing something?
await Task.Run(() => {
try
{
throw new Exception("test!");
}
catch (Exception ex)
{
Console.WriteLine("Catched");
}
});
Why not just catched in the Task.Run() ?
If you want to catch it in Console.Application with your code you can do it like this. But t.Wait() is blocking the UI Thread and waits for the Task to finish.
public static void Main(string[] args)
{
try
{
Task t = TestError();
t.Wait();
}
catch (Exception ex)
{
Console.WriteLine("Catched");
}
Console.ReadLine();
}
public static async Task TestError()
{
// Task.Run(() => GetMoreCodes(CodeBufferMaxSize));
// await Task.Run(() => GetMoreCodes(0));
await Task.Run(() => { throw new Exception("test!"); });
}
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 tried putting the try catch around the Task.WhenAll(tasks) but it did not catch anything. In one of my tasks I tried to artificially generate an exception by using Substring on an empty string. But all that happens is the app crashes and defaults to Application_UnhandledException.
private async void RunTasks()
{
tasks[0] = HttpExtensions.GetMyData("http://www....");
tasks[1] = HttpExtensions.GetMyData("http://www.....");
tasks[2] = GeoLocate.OneShotGeoLocate();
try
{
await Task.WhenAll(tasks);
}
catch (AggregateException ae)
{
App.ViewModel.ErrorMessage = ae.Message;
}
}
To get all exceptions thrown from execution of the tasks, use the following:
Task taskReturned = Task.WhenAll(taskArray);
try
{
await taskReturned;
}
catch
{
throw taskReturned.Exception;
}
This worked thanks to 'nakiya'
private async void RunTasks()
{
tasks[0] = HttpExtensions.GetMyData("http://www....");
tasks[1] = HttpExtensions.GetMyData("http://www.....");
tasks[2] = GeoLocate.OneShotGeoLocate();
try
{
await Task.WhenAll(tasks);
}
catch (Exception ae)
{
App.ViewModel.ErrorMessage = ae.Message;
}
}