Reputation: 1118
I've an application that is configured to catch any unobserved task exceptions having ThrowUnobservedTaskExceptions enabled="true" in app.config.
I've a library class(Class1) that needs to start an async task in it's constructor, but in some scenarios that throws exception and I get into UnobservedTaskException error when the instance of Class1 gets disposed(since that task is never awaited).
I fixed that by attaching a ContinueWith on the task in the constructor and handling the exception(by accessing Exception property of the task) with TaskContinuationOptions set to OnlyOnFaulted and this worked perfectly.
Now the issue I have is that this async task(that I initialize in constructor) is also being awaited in methods of this class as a validation check to ensure that task is completed before proceeding to rest of the code in the method. If I had called this method after instantiating my class(Class1) and if it throws an error, the ContinueWith that I had attached gets executed and the exception is handled. I don't want this behavior. I want it to throw the exception if it results in an error when being awaited inside a method.
I only want the unobservedtaskexceptions to be handled for this case only(not for the entire application) - which is the case when the Class1 is initialized and no methods called and if the task throws an exception, I handle it in ContinueWith. I don't want the code in ContinueWith to be executed when this task is being awaited inside a method and if that throws.
Here is the code that will provide more clarity. Please let me know if there is a way for me to achieve this.
Program.cs
using (Class1 c = new Class1())
{
c.ValidateInitializeAsync().Wait(); // I want this to throw. Only if this line is commented, I want the exception to be handled.
}
// The application needs to be run in Release mode in order for GC to dispose c and enter into the scenario I want
while (true)
{
Thread.Sleep(100);
GC.Collect();
GC.WaitForPendingFinalizers();
}
Class1.cs
class Class1 : IDisposable
{
public Task initializeTask;
public Class1()
{
this.initializeTask = TaskHelper.InlineIfPossible(() => RunTask()).ContinueWith(t =>
{
Console.WriteLine(string.Format("Exception handled, {0}", t.Exception.HResult));
}, TaskContinuationOptions.OnlyOnFaulted);
}
public async Task ValidateInitializeAsync()
{
await this.initializeTask;
}
public async Task RunTask()
{
await Task.Run(() =>
{
Console.WriteLine("Running task...");
Task.Delay(5000).Wait();
throw new InvalidOperationException("exception occured");
});
}
public void Dispose()
{
Console.WriteLine("Class1 disposed.");
}
}
static class TaskHelper
{
static public Task InlineIfPossible(Func<Task> function)
{
if (SynchronizationContext.Current == null)
{
return function();
}
else
{
return Task.Run(function);
}
}
}
Upvotes: 0
Views: 325
Reputation: 456417
What you're asking for isn't possible. Think about it as time moves forward: when RunTask
completes with an exception, the code must know right then whether to handle that exception. It can't look into the future and determine whether it will be handled by an await
further up the stack.
I recommend using an async factory pattern, so that any initialization exceptions are propagated immediately, before the calling code even gets the instance.
Upvotes: 1