Handling exception from non-awaited Task [duplicate] - c#

This question already has an answer here:
Exception handling in fire and forget for C# 5 (in .net 4.5)
(1 answer)
Closed 2 years ago.
Let's assume I have a console application with Main method, something like this:
public static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) =>
{
Console.WriteLine("App Unobserved");
};
TaskScheduler.UnobservedTaskException += (sender, eventArgs) =>
{
Console.WriteLine("Task Unobserved");
};
Task.Run(async () => await MyAwesomeMethod());
// other awesome code...
Console.ReadLine();
}
public static async Task MyAwesomeMethod()
{
// some useful work
if (something_went_wrong)
throw new Exception();
// other some useful work
}
So, I just run MyAwesomeMethod (fire-and-forget), and want to do some other job, but I also want to know if there any unhandled exceptions. But application finishes successfully without any sign of problem (exception is just swallowed).
How can I handle exception from MyAwesomeMethod(), without awaiting it or using Task.Run(...).Wait()?

So, I just run MyAwesomeMethod (fire-and-forget)... but I also want to know if there any unhandled exceptions. But application finishes successfully without any sign of problem (exception is just swallowed).
That's not "fire and forget", then. "Fire and forget" literally means that you don't care when (or whether) the task completes (or errors).
How can I handle exception from MyAwesomeMethod(), without awaiting it or using Task.Run(...).Wait()?
Use await anyway:
Task.Run(async () => {
try {
await MyAwesomeMethod();
} catch (Exception ex) {
Console.WriteLine(ex);
}
});

You can check the status of your task once it's done.
Task.Run(() => MyAwesomeMethod()).ContinueWith((task) =>
{
if (task.Status == TaskStatus.RanToCompletion && task.Result != null)
{
}
else
{
try
{
Logger.LogError(task.Exception.ToString());
Logger.LogMessage("something_went_wrong");
}
catch { }
}
});

You could for example wrap the code in the background task in a try...catch block and raise an event as soon as you enter the catch block (if you do).
Like
event EventHandler<Exception> exceptionInWorker;
and in the task do
try
{
//do something
}
catch (Exception e)
{
exceptionInWorker?.Invoke(this, e);
}

You can subscribe to TaskScheduler.UnobservedTaskException event as you do but with a handler that takes UnobservedTaskExceptionEventArgs as its second parameter, through it you could access the unhandled exception via its Exception property and log all info about it.

Related

Task swallows the exception thrown

In the method below, when an exception is thrown in the TRY block, it is being swallowed. How can I make it throw the exception so that it gets written to log in the catch block? The log writer works fine. Thanks!
public static bool MonitorQueueEmptyTask(string queueName, CancellationTokenSource tokenSource)
{
try
{
Task<bool> task = Task.Factory.StartNew<bool>(() =>
{
while (!QueueManager.IsQueueEmpty(queueName))
{
if (tokenSource.IsCancellationRequested)
{
break;
}
Thread.Sleep(5000);
throw new Exception("Throwing an error!"); //THIS THROW IS SWALLOWED -- NO LOG WRITTEN ON CATCH
};
return true;
}, tokenSource.Token);
}
catch (Exception ex)
{
WriteExceptionToLog(ex.Stack); //it's not that this method doesn't work. it works fine.
return false;
}
return true;
}
If you want to fire and forget, you can attach a continuation using ContinueWith. The current try-catch will not help you at all, as the exception is encapsulated inside the Task. If this is "fire and forget", than you can log the exception:
public static Task MonitorQueueEmptyTask(
string queueName, CancellationTokenSource tokenSource)
{
return Task.Factory.StartNew<bool>(() =>
{
while (!QueueManager.IsQueueEmpty(queueName))
{
if (tokenSource.IsCancellationRequested)
{
break;
}
Thread.Sleep(5000);
throw new Exception("Throwing an error!");
};
}, tokenSource.Token, TaskCreationOptions.LongRunning).ContinueWith(faultedTask =>
{
WriteExceptionToLog(faultedTask.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
}
This, in turn, will not propagate the exception after it's thrown, but will provide a mechanism to log the error. If you want the exception to be properly handled, you can register to TaskScheduler.UnobservedTaskException. Additionally, you can set ThrowUnobservedTaskExceptions enabled="true" in your configuration if you want unhandled exceptions to terminate your application. ContinueWith will consider the exception "handled" once you look at the task.Exception property.
The exception is not swallowed; it's just that it doesn't occur on the thread that executes the try/catch block, but on the separate Task thread.
If you don't observe the task's result or exception, when the task is eventually garbage collected, it will throw an exception saying that the task was not observed. Unless you catch that by handling the TaskScheduler.UnobservedTaskException, it will crash the process.
I also had a problem with this, and i really dislike the whole idea of App.config, so can provide another solution to prevent the exceptions disappearing :)
Save the exception then throw it after the Task.Run has completed, e.g.
private async void Function() {
Exception save_exception = null;
await Task.Run(() => {
try {
// Do Stuff
} catch (Exception ex) {
save_exception = ex;
}
}).ContinueWith(new Action<Task>(task => {
if (save_exception != null)
throw save_exception;
// Do Stuff
}));
}

TaskCanceledException with ContinueWith

I've been trying to figure out why I'm getting a TaskCanceledException for a bit of async code that has recently started misbehaving. I've reduced my issue down to a small code snippet that has me scratching my head:
static void Main(string[] args)
{
RunTest();
}
private static void RunTest()
{
Task.Delay(1000).ContinueWith(t => Console.WriteLine("{0}", t.Exception), TaskContinuationOptions.OnlyOnFaulted).Wait();
}
As far as I'm aware, this should simply pause for a second and then close. The ContinueWith won't be called (this only applies to my actual use-case). However, instead I'm getting a TaskCanceledException and I've no idea where that is coming from!
You are using the wrong taskcontinuationoption:
See following link : https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcontinuationoptions%28v=vs.110%29.aspx
It says :
Specifies that the continuation task should be scheduled only if its antecedent threw an unhandled exception. This option is not valid for multi-task continuations.
As guys said above this call requires just antecedent-task in faulted-status otherwise will throw TaskCanceledException, for this concrete case you can generalize ContinueWith to process all statuses:
await Task.Delay(1000).ContinueWith(
task =>
{
/* take into account that Canceled-task throw on next row the TaskCancelledException */
if (!task.IsFaulted) {
return;
}
Console.WriteLine("{0}", task.Exception);
// do smth like 'throw task.Exception.InnerException'
});
I also received this error:
The block of code looked like this:
private void CallMediator<TRequest>(TRequest request) where TRequest : IRequest<Unit>
{
_ = Task.Run(async () =>
{
var mediator = _serviceScopeFactory.CreateScope().ServiceProvider.GetService<IMediator>()!;
await mediator.Send(request).ContinueWith(LogException, TaskContinuationOptions.OnlyOnFaulted);
});
}
private void LogException(Task task)
{
if (task.Exception != null)
{
_logger.LogError(task.Exception, "{ErrorMessage}", task.Exception.Message);
}
}
Reading the documentation for the ContinueWith method, it has the following remarks:
The returned Task will not be scheduled for execution until the current task has completed. If the continuation criteria specified through the continuationOptions parameter are not met, the continuation task will be canceled instead of scheduled.
So for me, it called the first task (mediator.Send(request)), then it continued with the task ContinueWith(...), which is the one I awaited. However, since an exception had not occurred in the first task, the second task was cancelled. Therefore, when awaiting the second task, it threw a TaskCanceledException.
What I did, was to change the code to this:
private void CallMediator<TRequest>(TRequest request) where TRequest : IRequest<Unit>
{
_ = Task.Run(async () =>
{
var mediator = _serviceScopeFactory.CreateScope().ServiceProvider.GetService<IMediator>()!;
try
{
_ = await mediator.Send(request);
}
catch (Exception ex)
{
_logger.LogError(ex, "{ErrorMessage}", ex.Message);
}
});
}
Instead of using .ContinueWith(...), I have replaced it with just a regular try-catch block in case of the task I am interested in fails. I think this simplifies the code and makes it more readable.
In the question, there is this line of code:
Task.Delay(1000).ContinueWith(t => Console.WriteLine("{0}", t.Exception), TaskContinuationOptions.OnlyOnFaulted).Wait();
I would rewrite it to:
try
{
Task.Delay(1000).Wait();
}
catch (Exception ex)
{
Console.WriteLine("{0}", ex);
}

Handle exception thrown by a task

I have a task running a long time operation in WPF:
Task t = Task.Factory.StartNew(() =>
{
try
{
process(cancelTokenSource.Token, CompressionMethod, OpInfo);
}
catch (OperationCanceledException)
{
logger.Info("Operation cancelled by the user");
}
}, cancelTokenSource.Token);
try
{
t.Wait();
}
catch (AggregateException ae)
{
int i = 0;
}
private void process(CancellationToken token, CompressionLevel level, OperationInfo info)
{
// check hash
if (ComputeHash)
{
logger.Info("HASH CHECKING NOT IMPLEMENTED YET!");
MessageBox.Show(this,"HASH CHECKING NOT IMPLEMENTED YET!", "WARNING", MessageBoxButton.OK, MessageBoxImage.Warning);
}
token.ThrowIfCancellationRequested();
UserMsgPhase = "Operation finished";
return info;
}
Problem is "MessageBox.Show" throws an exception and it is not captured within "catch (AggregateException ae)". I've been reading about TPL exception handling but I don't understand why it is not catched. Please, could you help me?
Once the task is complete you can check its Exception property. You also have Status and IsCompleted properties which may be useful to you...
Check Task.Exception.
If your task is typed (returning a result), then accessing myTask.Result will throw this exception.
Moreover, if you are running .Net 4.5, you could use async/await.
As an example:
public async void MyButton_OnClick(object sender, EventArgs e)
{
try
{
Task t = ...your task...;
var myResult = await t; // do whatever you like with your task's result (if any)
}catch
{
// whatever you need
}
}
as you would do with synchronous code (but this is not an actual synchronous call)
I believe that the question's process method is a Task, so it looks like it could be implement in a different manner:
You can make the process to be implemented as Task and then you will have a task-child within task-parent.
Then you can make use of the TaskCreationOptions.AttachedToParent option.
According to Stephen Toub, using AttachedToParent will help notify children-task exception to the parent-task catch:
any exceptions from faulted children will propagate up to the parent
Task (unless the parent Task observes those exceptions before it
completes).
Example:
I've omitted the cancellation token parts in order for it to be more simple.
Task t = Task.Factory.StartNew(() =>
{
var process = new Task(() =>
{
//Copy here the process logic.
}, TaskCreationOptions.AttachedToParent);
//*Private failure handler*.
process.start();
});
try
{
t.Wait();
}
catch (AggregateException ae)
{
//handle exceptions from process.
}
In addition, you may add a private failure handler like:
//*Private failure handler*.
var failHandler = child.ContinueWith(t =>
{
//Oops, something went wrong...
}, TaskContinuationOptions.AttachedToParent|TaskContinuationOptions.OnlyOnFaulted);

How to propagate thread exceptions back to application exception handler

http://msdn.microsoft.com/en-us/magazine/gg598924.aspx
Why exceptions are not propagated by WPF Dispatcher.Invoke?
How can I allow Task exceptions to propagate back to the UI thread?
In the code below I need to propagate execeptions that are thrown in the tasks and their continuations back up to the ui thread where they will be handled by LogException. If I need to re-throw an exception somewhere along the line thats fine with me. Whatever works. How do I do that?
I referenced some questions that are similar to mine but I do not see an answer that is relevant to my app.
Edit 3: posted a simplified example
Edit 2:
See this:
http://msdn.microsoft.com/en-us/library/dd997415(v=vs.100).aspx
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
FireAndForget();
WaitOnTask();
}
private void FireAndForget()
{
Task t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(3000);
throw new Exception("boo");
});
Task c1 = t1.ContinueWith((t) =>
{
// The app global exception handler will not catch this.
}, TaskContinuationOptions.OnlyOnFaulted);
//MessageBox.Show("Task is running");
}
private void WaitOnTask()
{
Task t1 = Task.Factory.StartNew(() =>
{
throw new Exception("boo");
});
try
{
t1.Wait();
}
catch (Exception ex)
{
// The app global exception handler will catch this:
throw new Exception("Task", ex);
}
}
}
public partial class App : Application
{
public App()
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
Application.Current.DispatcherUnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(Current_DispatcherUnhandledException);
//System.Threading.Tasks.TaskScheduler.UnobservedTaskException += new EventHandler<System.Threading.Tasks.UnobservedTaskExceptionEventArgs>(TaskScheduler_UnobservedTaskException);
}
void TaskScheduler_UnobservedTaskException(object sender, System.Threading.Tasks.UnobservedTaskExceptionEventArgs e)
{
LogException(e.Exception);
}
void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
LogException(e.Exception);
}
void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
LogException(e.ExceptionObject as Exception);
}
private void LogException(Exception ex)
{
// log it
string error = "This app has encountered an unexpected error . The error message is:" + Environment.NewLine + ex.Message + Environment.NewLine;
Exception tmp = ex.InnerException;
while (tmp != null)
{
error += "Inner exception is: " + Environment.NewLine + tmp.Message + Environment.NewLine;
tmp = tmp.InnerException;
}
error += "Please press OK to exit.";
MessageBox.Show(error, "Error");
Environment.Exit(-1);
}
}
When you use StartNew or ContinueWith, any exceptions are placed on the returned Task.
There are two problems with marshaling exceptions:
Task.Exception wraps your exception in an AggregateException.
When you throw an exception later (e.g., on another thread), the original call stack is lost.
For the first problem, some people use the Flatten or Handle members to work directly with AggregateException. I prefer unwrapping the exceptions by dealing with Task.Exception.InnerException instead of Task.Exception.
For the second problem, some people work around it by wrapping it in another exception, but I have taken an alternative approach. .NET 4.5 introduced ExceptionDispatchInfo, which is the correct way to do this. In .NET 4.0 you can hack something like this:
public static Exception Rethrow(this Exception ex)
{
typeof(Exception).GetMethod("PrepForRemoting",
BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(ex, new object[0]);
throw ex;
}
I'm not sure if i'm missing something here, but if you use
TaskScheduler.FromCurrentSynchronizationContext() as the second parameter to ContinueWith
then it will be marshaled back onto your UX thread.
I actually wrote a blog post about it if you want a little more of a sample.
http://www.briankeating.net/post/Why-I-love-the-Task-library
Kr,
Brian.
The answer to the question is found here:
http://blogs.msdn.com/b/pfxteam/archive/2009/05/31/9674669.aspx
Basically there are two scenarios: Situations where you can wait on the task and situations where you cannot i.e. fire and forget.
In situations where you can wait on the task, wrap it in a try block as shown in the question and rethrow the error. The global app handler will catch it.
In situtions where you cannot wait on the task you have to call your logger manually. There is no application level handler that will catch the error. There is a possibility that TaskScheduler.UnobservedTaskException will fire, however that event is IMHO highly circumstantial and fragile and not a good option.
To propagate the exceptions in your code you need to Wait on all the tasks. If you make the following changes to your FireAndForget method the Exception in the nested Task will be propagated back to the calling thread.
private void FireAndForget()
{
var tasks = new Task[2];
tasks[0] = Task.Factory.StartNew(() =>
{
Thread.Sleep(3000);
throw new Exception("boo");
});
tasks[1] = tasks[0].ContinueWith((t) =>
{
throw new Exception("nested boo", tasks[0].Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
try
{
Task.WaitAll(tasks);
}
catch (AggregateException ex)
{
throw new Exception("Task", ex);
}
}
Of course this is no longer a "fire and forget" method. If waiting on the tasks is undesirable you will need to write to your log file from within the continuation.
You can await the completion of the task to receive exception from the task code.
try{
await Task.Factory.StartNew(() => throw Exception("hello"));
}catch{
// will get exception here
}

Catching Error when using Task.Factory

i am using the following
Task.Factory.StartNew(() => DoPrintConfigPage(serial));
then the function i am calling looks like this
private void DoPrintConfigPage(string serial)
{
//do printing work
}
My problem is an exception is being thrown inside the thread and not being handled.
I have tried wrapping it in a try catch
try
{
Task.Factory.StartNew(() => DoPrintConfigPage(serial));
}
catch (Exception ex) { }
but it still is not catching the error and thus crashing the application.
How can I catch exceptions in the main thread so I can handle them?
Update
I have made the changes recommended below and still it is saying the exception is unhandled
var task = Task.Factory.StartNew(() => DoPrintConfigPage(serial))
.ContinueWith(tsk =>
{
MessageBox.Show("something broke");
},TaskContinuationOptions.OnlyOnFaulted);
then in my DoConfigPage I added another try catch.
In this catch is now where it is crashing and saying the exception being thrown was unhandled, what am I missing?
private void DoPrintConfigPage(string serial)
{
try
{
//call the print function
}
catch (Exception ex)
{
throw ex; //it is crashing here and saying it is unhandled
}
}
I also tried what Eric J. suggested with the same results
var task = Task.Factory.StartNew(() => DoPrintConfigPage(serial));
try
{
task.Wait();
}
catch (AggregateException ex) { MessageBox.Show("something broke"); }
Alternatively, you can chain your task creation and add a ContinueWith:
var job = Task.Factory
.StartNew(...)
.ContinueWith(tsk =>
{
// check tsk for exception and handle
});
EDIT: This snippet, when run, pops up the message box for me:
void Main()
{
var serial = "some serial";
var task = Task.Factory
.StartNew(() => DoPrintConfigPage(serial))
.ContinueWith(tsk =>
{
MessageBox.Show("something broke");
var flattened = tsk.Exception.Flatten();
// NOTE: Don't actually handle exceptions this way, m'kay?
flattened.Handle(ex => { MessageBox.Show("Error:" + ex.Message); return true;});
},TaskContinuationOptions.OnlyOnFaulted);
}
public void DoPrintConfigPage(string serial)
{
throw new Exception("BOOM!");
}
Your try block is exited right after you start the new task, because that method just continues to run.
Instead you can catch the Exception as an AggregateException where you wait for the task (or multiple tasks) to complete:
var task1 = Task.Factory.StartNew(() =>
{
throw new MyCustomException("I'm bad, but not too bad!");
});
try
{
task1.Wait();
}
catch (AggregateException ae)
{
// Assume we know what's going on with this particular exception.
// Rethrow anything else. AggregateException.Handle provides
// another way to express this. See later example.
foreach (var e in ae.InnerExceptions)
{
if (e is MyCustomException)
{
Console.WriteLine(e.Message);
}
else
{
throw;
}
}
}
http://msdn.microsoft.com/en-us/library/dd997415.aspx
If you are not waiting on your task, I think the easiest solution is found in Task.Exception:
Gets the AggregateException that caused the Task to end prematurely.
If the Task completed successfully or has not yet thrown any
exceptions, this will return null.
I am using something like this:
Task.Factory.StartNew(() => DoStuffHere())
.ContinueWith(task =>
{
if (task.Exception != null)
Log("log all the exceptions!");
});
You should also know about
System.Threading.Tasks.TaskScheduler.UnobservedTaskException.
If you are in the business of creating "fire and forget" Task instances, you'll want to subscribe to that event at the start of your program.
Maybe you are trying to catch a Corrupted State Exception. Since .NET 4 applications are unable to catch such exceptions by default. You could try to add the legacyCorruptedState­­ExceptionsPolicy=true entry to your configuration file as stated in the MSDN article linked above.

Categories