Taskscheduler.Unobservedtaskexception Event Handler Never Being Triggered

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 awaited 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



Leave a reply



Submit