[等待Task
时,我想有一种简单的方法来忽略特定类型的异常,例如Task
或OperationCanceledException
或其他。我想到了编写一个扩展方法的方法,该方法可以包装我的TimeoutException
,并抑制我会给它作为参数的Task
类型。所以我写了这个:
Exception
我可以这样使用它,并且可以正常使用:
public static async Task Ignore<TException>(this Task task)
where TException : Exception
{
try
{
await task;
}
catch (Exception ex)
{
if (ex is TException) return;
throw;
}
}
问题是它仅支持一种类型的异常,我必须为两种类型编写另一种版本,另一种针对三种类型,另一种针对四种,依此类推。这已经失控了,因为我还想要此扩展程序的重载,它将忽略返回结果(await myTask.Ignore<OperationCanceledException>();
)的任务的异常。因此,我还需要另一套扩展方法来解决这种情况。
这是我的实现,用于在等待Task<Result>
时忽略一种异常类型:
Task<Result>
用法:
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;
}
}
我的问题是,我可以以能够处理多种类型的被忽略异常的方式编写这些方法,而不必为每种类型的数量编写单独的方法吗?
是,可以,但是不能使用var result = await myInt32Task.Ignore<int, OperationCanceledException>(0);
。
如果您愿意将Generics
传递为Type
,则可以这样做:
params
现在,这没有吸引力了[[和您没有public static async Task<TResult> Ignore<TResult>
(this Task<TResult> task, TResult defaultValue, params Type[] typesToIgnore)
{
try
{
return await task;
}
catch (Exception ex)
{
if (typesToIgnore.Any(type => type.IsAssignableFrom(ex.GetType())))
{
return defaultValue;
}
throw;
}
}
(generic constraint
...),但应该可以完成工作。
where TException
public static Task<TResult> Ignore<TResult>(this Task<TResult> self, TResult defaultValue, params Type[] typesToIgnore)
{
return self.ContinueWith(
task =>
{
if (task.IsCanceled
&& (typesToIgnore.Any(t => typeof(OperationCanceledException) == t || t.IsSubclassOf(typeof(OperationCanceledException))))) {
return defaultValue;
}
if (!task.IsFaulted)
{
return task.Result;
}
if (typesToIgnore.Any(t => task.Exception.InnerException.GetType() == t ||
task.Exception.InnerException.GetType().IsSubclassOf(t)))
{
return defaultValue;
}
throw task.Exception.InnerException;
}, TaskContinuationOptions.ExecuteSynchronously);
}
时能够忽略不止一种类型的异常。您自己的解决方案似乎是我的最佳选择。您始终可以使用建议的解决方案简单地“链接”呼叫:Task
这应该返回一个Task,它实质上是三个嵌套的try-catch块。除非您active想要更优雅的definition,否则总可以摆脱更优雅的usage;)
唯一不太好的问题是,在await myTask.Ignore<OperationCanceledException>().Ignore<IOException>().Ignore<TimeoutException>();
返回TResult
的情况下,您必须多次“传播”默认值。如果这不是一个很大的问题,那么您可以摆脱相同的问题:
Task
作为“晦涩的”奖励,请注意,通过这种方式,您可以非常容易地为不同的异常提供不同的默认返回值。因此,必须重复默认值毕竟可以为您带来好处!例如,您可能有理由在TimeOutException上返回0,而在OperationCanceledException上返回-1,依此类推。如果最后这成为您的目的,请记住使用
更新await myTask.Ignore<int, OperationCanceledException>(0).Ignore<int, TimeoutException>(0);
可能不是您真正想要的,而不是确切的is
相等,因为您可能还希望针对源自同一Type
的不同异常返回不同的默认值(这种分析开始变得相当复杂,但是您当然知道了)。基于
Type
的版本的链式调用“优雅”的最终水平似乎必须以编译时类型检查为代价:
TResult
如果您准备牺牲编译时的安全性,则可以通过仅声明您希望忽略的异常并最后添加“ casting”调用来减轻重复的痛苦。当然,这有其自身的问题,但是仅在需要忽略多个异常时才需要这样做。否则,您可以使用一个单一的类型并相应地调用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; }
。编辑
基于
Ignore<TResult, TException>()
,因为异步/等待模式似乎产生了一个新的Task,该任务包装了在上面的Ignore方法中传递的awaitable task参数,因此示例调用实际上以relevant comment失败,因为中间的Ignore调用事实已更改任务和原始任务在呼叫链中的某个位置丢失了。因此,已对“ casting”
InvalidCastException
方法进行了稍许调整,以使最后可以返回原始任务,从而可以在所有Ignore
调用之后,最后一次基于Ignore
的[ C0]通话。上面的代码已被修改以纠正这种情况。这不会使整个模式特别优雅,但至少现在似乎可以正常使用。
TResult
中指出,可以链接我原来的一种类型的方法来实现忽略多种类型的异常。但是,由于需要重复Ignore
类型和answer,因此将Ignore
与Task<TResult>
链接起来很麻烦。阅读TResult
的可接受答案后,我想出了解决方法。我需要介绍一个通用的任务包装器defaultValue
,它将保留此状态并包含可链接的方法a question about partial type inference。这是预期的用法:struct
这里是我命名为Ignore
的任务包装程序,以及扩展方法var result = await myInt32Task.WithDefaultValue(0) .Ignore<OperationCanceledException>() .Ignore<TimeoutException>();
。
TaskWithDefaultValue