I used this example in one of my projects, but there were quite an mount of edge cases that were not covered. Since I needed this operation to serve as a core method, I took the time to make it a bit more product ready. Here you are the version I ended with, it met the expectations of my test battery
/// <summary>
/// https://stackoverflow.com/q/11839959/17780206 - Rethrowing previous exception inside ContinueWith
/// </summary>
public static Task<TResult> ContinueWithEx<T, TResult>(this Task<T> task, Func<Task<T>, TResult> continuation)
{
// we want to run continuations asynchronously so that we don't block the thread, this way we can avoid deadlocks
var tcs = new TaskCompletionSource<TResult>(TaskCreationOptions.RunContinuationsAsynchronously);
// we use the default scheduler to avoid deadlocks, this is mostly important for UI applications
var taskScheduler = TaskScheduler.Default;
task.ContinueWith(t => {
if (t.IsFaulted)
{
var exception = t.Exception?.InnerExceptions.Count == 1 ? t.Exception.InnerExceptions[0] : t.Exception;
if (!tcs.TrySetException(exception!))
{
// this is not expected to happen, but if it does, log it
Logger.Warning("Failed to set exception because state of the task is already set", exception);
}
}
else if (t.IsCanceled)
{
if (!tcs.TrySetCanceled())
{
// this is not expected to happen, but if it does, log it
Logger.Warning("Failed to set canceled because state of the task is already set");
}
}
else
{
// this try catch is important to catch exceptions that might be thrown by the continuation
// if an exception is thrown, we want to set the exception on the task completion source
// if we would not do that, the code will freeze in such occurence because the task is never marked as completed/cancelled/failed
try
{
var result = continuation(t);
if (!tcs.TrySetResult(result))
{
// this is not expected to happen, but if it does, log it
Logger.Warning("Failed to set result because state of the task is already set");
}
}
catch (Exception ex)
{
if (!tcs.TrySetException(ex))
{
// this is not expected to happen, but if it does, log it
Logger.Warning("Failed to set exception because state of the task is already set", ex);
}
}
}
}, taskScheduler);
return tcs.Task;
}