ContinueWith and TaskCancellation - How to return default values ​​if the task is not completed?

I read several topics about TaskCancellations .. However, I can not find a solution for a simple question: how to get the default value when my task fails?

I can’t (!) Change the task itself and put a try catch chip around it. I could, of course, play with try-catch, but I would like to handle this with ContinueWith - if possible.

 public Task<List<string>> LoadExample() { Task<List<string>> task = LoadMyExampleTask(); task.ContinueWith(t => default(List<string>), TaskContinuationOptions.OnlyOnFaulted); return task; } 

I thought this would be the right way to deal with this problem. However, my application throws a JsonParseException (which is called in LoadMyExampleTask ). I would expect to get a null or (even better) empty list.

In fact, all I want is:

 var emptyOrFilledList = await LoadExample(); // guaranteed no exception thrown 

Based on Luaan's excellent answer, I wrote an extension method with the defaultValue parameter:

 public static Task<T> DefaultIfFaulted<T>(this Task<T> @this, T defaultValue = default(T)) { return @this.ContinueWith(t => t.IsCompleted ? t.Result : defaultValue); } 

Edit: await myTask.DefaultifFaulted() just threw it away

[ERROR] FATAL UNHANDLED EXCEPTION: System.AggregateException

Are you sure all exceptions are caught?

+8
c # task-parallel-library task
source share
2 answers

As promised, here are the options for DefaultIfFaulted<T> that are true for their name (and the name of this question). They preserve the antecedent behavior of the task if it does not work (in particular, the cancellation is propagated, but not ignored or masked with an AggregateException ):

Old School (.NET 4.0):

 public static Task<T> DefaultIfFaulted<T>(this Task<T> task) { // The continuation simply returns the antecedent task unless it faulted. Task<Task<T>> continuation = task.ContinueWith( t => (t.Status == TaskStatus.Faulted) ? Task.FromResult(default(T)) : t, TaskContinuationOptions.ExecuteSynchronously ); return continuation.Unwrap(); } 

Async / await (simple but slow):

 public static async Task<T> DefaultIfFaulted<T>(this Task<T> task) { try { return await task.ConfigureAwait(false); } catch (Exception ex) when (!(ex is OperationCanceledException)) { return default(T); } } 

Async / await way (perf is almost identical to Unwrap ):

 public static async Task<T> DefaultIfFaulted<T>(this Task<T> task) { // Await completion regardless of resulting Status (alternatively you can use try/catch). await task .ContinueWith(_ => { }, TaskContinuationOptions.ExecuteSynchronously) .ConfigureAwait(false); return task.Status != TaskStatus.Faulted // This await preserves the task behaviour // in all cases other than faulted. ? await task.ConfigureAwait(continueOnCapturedContext: false) : default(T); } 

Tests (passed all of the above):

 using Xunit; [Fact] public async Task DefaultIfFaultedTest() { var success = Task.Run(() => 42); var faulted = Task.Run(new Func<int>(() => { throw new InvalidOperationException(); })); Assert.Equal(42, await success.DefaultIfFaulted()); Assert.Equal(0, await faulted.DefaultIfFaulted()); await Assert.ThrowsAsync<TaskCanceledException>(() => { var tcs = new TaskCompletionSource<int>(); tcs.SetCanceled(); return tcs.Task.DefaultIfFaulted(); }); } 
+3
source share

If you want this, you should not return the original task - you need to return the continuation.

 public Task<List<string>> LoadExample() { Task<List<string>> task = LoadMyExampleTask(); return task.ContinueWith(t => t.IsFaulted || t.IsCanceled ? default(List<string>) : t.Result); } 

Your source code allowed you to continue execution when the original task failed, but you did not read the status of this task - the fact that the task has a continuation that handles errors is completely irrelevant to the fact that await for the original task.

Of course, it's pretty easy to do this in a general helper method:

 public static Task<T> DefaultIfFaulted<T>(this Task<T> @this) { return @this.ContinueWith (t => t.IsCanceled || t.IsFaulted ? default(T) : t.Result); } 
+9
source share

All Articles