From what I understand, your desire is to be able to ignore more than 1 type of exceptions while awaiting your Task
. Your own solution appears to be your best option for me. You could always simply "chain" the calls using your proposed solution:
await myTask.Ignore<OperationCanceledException>().Ignore<IOException>().Ignore<TimeoutException>();
This should return a Task that is, in essence, three nested try-catch blocks. Unless you actively want a more elegant definition, you can always get away with more elegant usage ;)
The only not-so-elegant problem is that, in the case of your TResult
-returning Task
s, you have to "propagate" the default value multiple times. If this is not a very big problem, then you could get away with the same:
await myTask.Ignore<int, OperationCanceledException>(0).Ignore<int, TimeoutException>(0);
As an "obscure" bonus, note that this way you can very easily supply different default return values for different exceptions. So having to repeat the default value might turn to your advantage after all! For example you might have a reason to return 0 on TimeOutException but -1 on OperationCanceledException, etc. If this becomes your purpose in the end, remember that using is
might not be what you really want, rather than exact Type
equality, because you might want to also return different default values for different exceptions that derive from the same Type
(this analysis is starting to get rather complicated but you get the point, of course).
Update
The ultimate level of chained-call "elegance" for the TResult
-based version seems to have to come at the expense of compile-time type checking:
public static async Task<TResult> Ignore<TResult, TException>(
this Task<TResult> task, TResult defaultValue)
where TException : Exception
{
try
{
return await task;
}
catch (Exception ex)
{
if (ex is TException) return defaultValue;
throw;
}
}
public static async Task<TResult> Ignore<TResult, TException>(
this Task task, TResult defaultValue)
where TException : Exception
{
try
{
return await (Task<TResult>)task;
}
catch (Exception ex)
{
if (ex is TException) return defaultValue;
throw;
}
}
public static Task Ignore<TException>(this Task task)
where TException : Exception
{
try
{
//await seems to create a new Task that is NOT the original task variable.
//Therefore, trying to cast it later will fail because this is not a Task<TResult>
//anymore (the Task<TResult> has been "swallowed").
//For that reason, await the task in an independent function.
Func<Task> awaitableCallback = async () => await task;
awaitableCallback();
//And return the original Task, so that it can be cast back correctly if necessary.
return task;
}
catch (Exception ex)
{
//Same upon failure, return the original task.
if (ex is TException) return task;
throw;
}
}
public static async Task<int> TestUse()
{
Task<int> t = Task<int>.Run(() => 111);
int result = await t.Ignore<TaskCanceledException>()
.Ignore<InvalidOperationException>()
.Ignore<int, TimeoutException>(0);
return result;
}
If you are prepared to sacrifice compile-time safety, you can ease the pain of repetition by only stating the exceptions you wish to ignore and adding the "casting" call in the end. This has its own share of problems, of course, but you only need to do it when you need to ignore multiple exceptions. Otherwise, you are good with a single type and the corresponding single call to Ignore<TResult, TException>()
.
Edit
Based on the relevant comment, because the async/await pattern appears to spawn a new Task that wraps the awaitable task parameter passed in the Ignore methods above, the example call indeed fails with an InvalidCastException
as the intermediate Ignore calls in fact changed the Task and the original Task gets lost somewhere in the chain of calls. Therefore, the "casting" Ignore
method has been re-adapted slightly to enable returning the original task at the end, so that it can successfully be cast back after all Ignore
calls, by the last TResult
-based Ignore
call. The code above has been amended to correct this scenario. This does not make the entire pattern particularly elegant but at least it seems to be working properly now.