TaskScheduler.UnobservedTaskException event handler never being triggered
Unfortunately, that example will never show you your code. The UnobservedTaskException
will only happen if a Task gets collected by the GC with an exception unobserved - as long as you hold a reference to task1
and task2
, the GC will never collect, and you'll never see your exception handler.
In order to see the behavior of the UnobservedTaskException
in action, I'd try the following (contrived example):
public static void Main()
{
TaskScheduler.UnobservedTaskException += (object sender, UnobservedTaskExceptionEventArgs eventArgs) =>
{
eventArgs.SetObserved();
((AggregateException)eventArgs.Exception).Handle(ex =>
{
Console.WriteLine("Exception type: {0}", ex.GetType());
return true;
});
};
Task.Factory.StartNew(() =>
{
throw new ArgumentNullException();
});
Task.Factory.StartNew(() =>
{
throw new ArgumentOutOfRangeException();
});
Thread.Sleep(100);
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Done");
Console.ReadKey();
}
This will show you your messages. The first Thread.Sleep(100)
call provides enough time for the tasks to throw. The collect and wait forces a GC collection, which will fire your event handler 2x.
Why is the UnobservedTaskException event handler not triggered for a Task which throws an exception?
You are not waiting for the task to finish executing. That means that your app will end before the task throws an exception.
If you simply add a Thread.Sleep
and let the task throw the exception you will be notified:
private static void Main()
{
SetupUnobservedTaskExceptionHandling();
Task.Factory.StartNew(() =>
{
var counter = 5;
for (; counter > 0; counter--)
{
Console.WriteLine(counter);
Thread.Sleep(1000);
}
throw new InvalidCastException("I threw up!");
});
Thread.Sleep(10000);
GC.Collect();
GC.WaitForPendingFinalizers();
Console.ReadLine();
}
Why is the TaskScheduler.UnobservedTaskException does not trigger?
To achievethe expected behavior you can follow the comments above and remove GC.KeepAlive(task)
while using Release
or
you can set task = null;
before calling GC.Collect();
to ensure that task is collected.
Here you can find another related answer that maybe add some more detail.
UnobservedTaskException being throw but it is handled by a TaskScheduler.UnobservedTaskException handler and a continuations OnlyOnFaulted handler
The solution was based on How to handle all unhandled exceptions when using Task Parallel Library?
class Program
{
static void Main(string[] args)
{
Program p = new Program();
p.tplTestOne();
}
public void tplTestOne()
{
//-------------------------------------------------
MyClassHere.onUnobservedTaskException += (object sender, EventException e) =>
{
Console.WriteLine(e.Exception.Message); //its fired OK
};
TaskScheduler.UnobservedTaskException += (object sender, UnobservedTaskExceptionEventArgs e) =>
{
Console.WriteLine(e.Exception.Message); // its not fired, buggy
};
//-------------------------------------------------
CancellationTokenSource source = new CancellationTokenSource();
Task tz = MyClassHere.CreateHandledTask(
new TaskScheduled(0, () => {
if (!source.IsCancellationRequested)
{
Console.WriteLine("A-main-task-started");
}
Thread.Sleep(5000);
if (source.IsCancellationRequested)
{
Console.WriteLine("CancelingMainTask");
}
})
, new TaskScheduled(3000, () => { Console.WriteLine("okTaskCalled"); })
, null //new TaskScheduled(0, () => { Console.WriteLine("cancelTaskCalled"); })
, TaskCreationOptions.AttachedToParent
, source.Token
, new TaskScheduled(2000, () =>
{
if (!source.IsCancellationRequested)
{
Console.WriteLine("B-timeout");
}
})
, new TaskScheduled(1000, () =>
{
if (!source.IsCancellationRequested)
{
Console.WriteLine("C-timeout");
}
source.Cancel();
})
);
if(tz != null)
{
tz.ContinueWith(t => { Console.WriteLine("END"); });
}
Task tsk_1 = MyClassHere.createHandledTask(() =>
{
double x = 1;
x = (x + 1) / x;
}, false);
Task tsk_2 = MyClassHere.createHandledTask(() =>
{
double y = 0;
throw new Exception("forced_divisionbyzerodontthrowanymore_test"); // here -> System.Exception was unhandled by user code
}, true);
Task tsk_3 = MyClassHere.createHandledTask(() =>
{
double z = 1;
z = (z + 1) / z;
}, true);
Task tsk_4 = MyClassHere.createHandledTask(() =>
{
double k = 1;
k = (k + 1) / k;
}, true);
Console.ReadLine();
}
}
public class EventException : EventArgs
{
public Exception Exception;
public Task task;
public EventException(Exception err, Task tsk)
{
Exception = err;
task = tsk;
}
}
public class TaskScheduled
{
public int waitTime;
public Action action;
public DateTime datestamp;
public bool isCalled = false;
public TaskScheduled(int _waitTime, Action _action)
{
this.waitTime = _waitTime;
this.action = _action;
}
}
public static class MyClassHere
{
public delegate void UnobservedTaskException(object sender, EventException e);
public static event UnobservedTaskException onUnobservedTaskException;
//-------------------------------------------------
public static void waitForTsk(Task t)
{
try
{
t.Wait();
}
catch (AggregateException ae)
{
ae.Handle((err) =>
{
throw err;
});
}
}
//-------------------------------------------------
public static void RaiseUnobsrvEvtForEachIfHappens(this Task task)
{
task.ContinueWith(t =>
{
var aggException = t.Exception.Flatten();
foreach (var exception in aggException.InnerExceptions)
{
onUnobservedTaskException(task, new EventException(exception, task));
}
},
TaskContinuationOptions.OnlyOnFaulted); // not valid for multi task continuations
}
//-------------------------------------------------
public static Task CreateHandledTask(Action action)
{
return CreateHandledTask(action, false);
}
public static Task CreateHandledTask(Action action, bool attachToParent)
{
Task tsk = null;
tsk = CreateHandledTask(action, attachToParent, CancellationToken.None);
return tsk;
}
public static Task CreateHandledTask(Action action, bool attachToParent, CancellationToken cancellationToken)
{
Task tsk = null;
TaskCreationOptions atp = TaskCreationOptions.None;
if (attachToParent) { atp = TaskCreationOptions.AttachedToParent; }
tsk = CreateHandledTask(action, atp, cancellationToken);
return tsk;
}
public static Task CreateHandledTask(Action action, TaskCreationOptions tco, CancellationToken cancellationToken)
{
Task tsk = null;
tsk = Task.Factory.StartNew(action, cancellationToken, tco, TaskScheduler.Default);
tsk.RaiseUnobsrvEvtForEachIfHappens();
return tsk;
}
public static Task CreateHandledTask(TaskScheduled mainTask,
TaskScheduled onSuccessTask,
TaskScheduled onCancelationTask,
TaskCreationOptions tco,
CancellationToken cancellationToken,
params TaskScheduled[] timeouts)
{
Task tsk = null;
ManualResetEvent me = new ManualResetEvent(false);
if (timeouts == null || timeouts.Length < 1 || timeouts[0] == null)
{
tsk = CreateHandledTask(mainTask.action, tco, cancellationToken);
me.Set();
}
else
{
bool isCancelation = false;
bool isSuccess = true;
Task NonBlockCtxTask = CreateHandledTask(() =>
{
tsk = CreateHandledTask(mainTask.action, tco, cancellationToken);
me.Set();
int qtdt = timeouts.Count(st => st.action != null);
CountdownEvent cde_pas = new CountdownEvent(3);
CountdownEvent cde_pat = new CountdownEvent(qtdt);
Parallel.ForEach<TaskScheduled>(timeouts, (ts) =>
{
try
{
bool itsOnTime = tsk.Wait(ts.waitTime, cancellationToken);
cde_pat.Signal();
if (!itsOnTime)
{
isSuccess = false;
Task tact = CreateHandledTask(ts.action, TaskCreationOptions.None, cancellationToken);
}
}
catch (OperationCanceledException oce)
{
isSuccess = false;
cde_pat.Signal(cde_pat.CurrentCount);
isCancelation = true;
}
});
try
{
isSuccess &= cde_pat.Wait(System.Threading.Timeout.Infinite, cancellationToken) && !isCancelation;
}
catch (OperationCanceledException oce)
{
isCancelation = true;
isSuccess = false;
}
finally
{
cde_pas.Signal();
}
try
{
if (isCancelation && onCancelationTask != null)
{
Thread.Sleep(onCancelationTask.waitTime);
Task tcn = CreateHandledTask(onCancelationTask.action);
}
}
catch { }
finally {
cde_pas.Signal();
}
try
{
if (isSuccess && onSuccessTask != null)
{
Thread.Sleep(onSuccessTask.waitTime);
Task tcn = CreateHandledTask(onSuccessTask.action);
}
}
catch { }
finally
{
cde_pas.Signal();
}
cde_pas.Wait(System.Threading.Timeout.Infinite);
}, TaskCreationOptions.None, cancellationToken);
}
me.WaitOne();
return tsk;
}
//-------------------------------------------------
}
Using the UnobservedTaskException handler in dotnetcoreapp2.0
I had the same issue. Just try it run in RELEASE mode. I tested it and it's working with console application .net core version 2.2.
internal class Program
{
private static void Main(string[] args)
{
// REMEMBER TO RUN IN RELEASE MODE
var handler = new EventHandler<UnobservedTaskExceptionEventArgs>(Unobserved);
TaskScheduler.UnobservedTaskException += handler;
Task.Run(() => { Console.WriteLine("task 1"); throw new Exception("TASK 1 EXCEPTION"); });
Task.Run(() => { Console.WriteLine("task 2"); throw new Exception("TASK 2 EXCEPTION"); });
Thread.Sleep(1000);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Thread.Sleep(1000);
Console.ReadKey();
}
private static void Unobserved(object o, UnobservedTaskExceptionEventArgs e)
{
e.SetObserved(); // optional
foreach (var ex in e.Exception.InnerExceptions)
{
Console.WriteLine(ex.Message);
}
}
}
system.threading.task - why does TaskScheduler.UnobservedTaskException event not occur? Can I fix this?
From the comment:
I can see that there is a reason for everything, but I can't deny
being flabbergasted by the idea that there's a good reason that this
should not happen by design. What if I don't want to await a Task,
should I just never use the Task class in that case? What's the
drawback to this having an event that can be subscribed to? I'm going
to write an extension method, and by all means, tell me why it
shouldn't be this way
The nature of the Task
object is that it is something that gets completed in the future. The result or exception of the task is also expected to get observed in the future, quite likely on a different stack frame or even on a different thread. That's why the Framework (or compiler-generated code, for async Task
methods) stores the exception inside the task and doesn't throw it immediately.
You do not observe the result of the task here and you do not even store a reference to the task object anywhere. Essentially, you're doing a fire-and-forget call. The compiler warns about that (telling that the task is not awaited), but it doesn't eliminate the fact the the managed Task
object still gets created and is hanging around until it gets garbage-collected. At that point, TaskScheduler.UnobservedTaskException
event will be fired.
... there's a good reason that should not happen by design
Now if the above approach is a bad design, what would be a good one? If the exception would be thrown immediately, how could the following possibly work:
var task = Task.Run(() => {
throw new ApplicationException("I'll throw an unhanded exception"); });
Thread.Sleep(1000);
if (task.IsFaulted)
Console.WriteLine(task.Exception.InnerException.Message);
It simply could not. The code after Sleep
would not have a chance to handle the exception its way. Thus, the current behavior is well thought out and makes perfect sense.
If you still want to observe exception as soon as it happens for a fire-and-forget task, use a helper async void
method:
public static class TaskExt
{
public static async void Observe(
this Task @this,
bool continueOnCapturedContext = true)
{
await @this.ConfigureAwait(continueOnCapturedContext);
}
}
static void Main(string[] args)
{
TaskScheduler.UnobservedTaskException += Handler;
AppDomain.CurrentDomain.UnhandledException += Handler;
Task.Run(() => { throw new ApplicationException("I'll throw an unhanded exception"); })
.Observe();
Task.Factory.StartNew(() => { throw new ApplicationException("I'll throw an unhanded exception too"); })
.Observe();
System.Threading.Thread.Sleep(2000);
Console.WriteLine("I think everything is just peachy!");
System.Threading.Thread.Sleep(10000);
}
You can also use an async void
lambda for a fire-and-forget call:
Action fireAndForget = async () =>
{
try
{
await Task.Run(...);
}
catch(e) { /* handle errors */ };
};
fireAndForget();
I described the exception propagation behavior for async void
methods in more details here.
OK, I get what you're saying. By design, are tasks meant to be never
used with a fire and forget pattern? Just dispatch the work to an
anonymous thread some other way?
I'm not saying that tasks aren't meant to be used with fire-and-forget pattern. I actually think they're perfectly suited for that. There are some options out there to implement this pattern with tasks, I showed one above, another one is Task.ContinueWith
, or you can check this question for some more ideas.
A different matter is that I hardly can imagine a useful scenario where I would not want to observe the completion status of the task, even for a fire-and-forget call like above. What job would such a task do? Even for logging, I'd still want to make sure a log enty was successfully written. Besides, what if the parent process ends before the task is complete? In my experience, I've always used something like QueueAsync
to keep tracks of the fired tasks.
Using TaskScheduler.UnobservedTaskException can avoid the process being killed?
You need to call UnobservedTaskExceptionEventArgs.SetObserved
to make it an observed exception:
TaskScheduler.UnobservedTaskException += (o, ev) =>
{
Console.WriteLine(ev.Exception);
Console.WriteLine("---------");
ev.SetObserved();
};
How to crash on unhandled Task exception?
The problem is correctly identified by Jon Skeet in his comment to the original post.
The best resource I found concerning this topic is by Stephen Toub.
tldr:
"To make it easier for developers to write asynchronous code based on Tasks, .NET 4.5 changes the default exception behavior for unobserved exceptions. While unobserved exceptions will still cause the UnobservedTaskException event to be raised (not doing so would be a breaking change), the process will not crash by default. Rather, the exception will end up getting eaten after the event is raised, regardless of whether an event handler observes the exception. This behavior can be configured, though. A new CLR configuration flag may be used to revert back to the crashing behavior of .NET 4, e.g."
<configuration>
<runtime>
<ThrowUnobservedTaskExceptions enabled=”true”/>
</runtime>
</configuration>
How to catch an exception in a task?
Since your Task.Run
is not await
ed that's why it is considered as a fire and forget task. The Task
itself can not throw exception (it only populates its Exception
property), the await
or .GetAwaiter().GetResult()
can throw.
The TaskScheduler
exposes an event called UnobservedTaskException, which is raised whenever your Task is collected by the GC and the Task is failed. The UnobservedTaskExceptionEventArgs
exposes an Exception
property which contains the unobserved exception.
For more details please check this SO thread: TaskScheduler.UnobservedTaskException event handler never being triggered.
Related Topics
How to Kill a Session or Session Id (Asp.Net/C#)
How to Remove Specific Style Tag in HTML Using C#
Local Variable and Expression Trees
Asp.Net Jquery Ajax Calling Code-Behind Method
How to Convert Style Text to C# Object Such as Class/Hashtable/Collection
Passing Eval from Aspx to JavaScript Function as Parameter
Are Java and C# Regular Expressions Compatible
Entity Framework Initialization Is Slow -- What How to Do to Bootstrap It Faster
How to Determine The Screen Width/Height Using C#
C# Generics Compared to C++ Templates
Duplicate Key Exception from Entity Framework
Rijndael 256 Encrypt/Decrypt Between C# and PHP
How to Generate Web Service Out of Wsdl
Difference Between SQLdatareader.Read and SQLdatareader.Nextresult
How to Set PDF Paragraph or Font Line-Height with Itextsharp
How Does Java's Use-Site Variance Compare to C#'s Declaration Site Variance