在实现返回任务以抛出异常的方法时,是否存在MS“最佳实践”或合同协议?这是在编写单元测试时出现的,我试图弄清楚我是否应该测试/处理这种情况(我认识到答案可能是“防御性编码”,但我不希望这是答案)。< / p>
即
方法必须始终返回一个任务,该任务应包含抛出的异常。
方法必须始终返回一个Task,除非该方法提供无效参数(即ArgumentException)。
方法必须始终返回一个任务,除非开发人员变得流氓并做他/她想要的事情(jk)。
Task Foo1Async(string id){
if(id == null){
throw new ArgumentNullException();
}
// do stuff
}
Task Foo2Async(string id){
if(id == null){
var source = new TaskCompletionSource<bool>();
source.SetException(new ArgumentNullException());
return source.Task;
}
// do stuff
}
Task Bar(string id){
// argument checking
if(id == null) throw new ArgumentNullException("id")
try{
return this.SomeService.GetAsync(id).ContinueWith(t => {
// checking for Fault state here
// pass exception through.
})
}catch(Exception ex){
// handling more Fault state here.
// defensive code.
// return Task with Exception.
var source = new TaskCompletionSource<bool>();
source.SetException(ex);
return source.Task;
}
}
答案 0 :(得分:6)
我最近问过一个类似的问题:
Handling exceptions from the synchronous part of async method
如果方法具有async
签名,则抛出方法的同步或异步部分无关紧要。在这两种情况下,异常都将存储在Task
内。唯一的区别是,在前一种情况下,生成的Task
对象将立即完成(出现故障)。
如果方法没有async
签名,则可能会在调用者的堆栈帧上抛出异常。
IMO,在任何一种情况下,调用者都不应该对同步或异步部分是否抛出异常,或者该方法是否具有async
签名做出任何假设
如果您确实需要知道任务是否已同步完成,则可以随时查看其Task.Completed
/ Faulted
/ Cancelled
状态或Task.Exception
属性,而无需等待:
try
{
var task = Foo1Async(id);
// check if completed synchronously with any error
// other than OperationCanceledException
if (task.IsFaulted)
{
// you have three options here:
// 1) Inspect task.Exception
// 2) re-throw with await, if the caller is an async method
await task;
// 3) re-throw by checking task.Result
// or calling task.Wait(), the latter works for both Task<T> and Task
}
}
catch (Exception e)
{
// handle exceptions from synchronous part of Foo1Async,
// if it doesn't have `async` signature
Debug.Print(e.ToString())
throw;
}
然而,通常你应该只await
result
,而不关心任务是否同步或异步完成,以及可能抛出的部分。将在调用者上下文中重新抛出任何异常:
try
{
var result = await Foo1Async(id);
}
catch (Exception ex)
{
// handle it
Debug.Print(ex.ToString());
}
这也适用于单元测试,只要async
方法返回Task
(单元测试引擎不支持async void
方法,AFAIK,这是有意义的:那里没有Task
来跟踪和await
)。
回到你的代码,我会这样说:
Task Foo1Async(string id){
if(id == null) {
throw new ArgumentNullException();
}
// do stuff
}
Task Foo2Async(string id) {
if(id == null){
throw new ArgumentNullException();
}
// do stuff
}
Task Bar(string id) {
// argument checking
if(id == null) throw new ArgumentNullException("id")
return this.SomeService.GetAsync(id);
}
让Foo1Async
,Foo2Async
,Bar
的来电者处理异常,而不是手动捕获和传播它们。
答案 1 :(得分:5)
我知道Jon Skeet喜欢在单独的同步方法中进行前置条件检查,以便直接抛出它们。
然而,我自己的观点是“无所谓”。考虑一下Eric Lippert的exception taxonomy。我们都同意外部异常应放在返回的Task
上(不直接抛在调用者的堆栈帧上)。应完全避免烦恼的异常。所讨论的唯一类型的例外是愚蠢的例外(例如,参数例外)。
我的论点是,抛出它们并不重要,因为你不应该编写能够捕获它们的生产代码。您的单元测试是唯一应该捕获ArgumentException
和朋友的代码,如果您使用await
那么它们何时被抛出并不重要。
答案 2 :(得分:3)
方法返回任务的一般情况是因为它们是异步方法。在这些情况下,通常应该像在任何其他方法中一样抛出方法的同步部分中的异常,并且异步部分中的异常应该存储在返回的Task
内(通过调用{{1}自动存储)方法或匿名委托)。
因此,在像无效参数这样的简单情况下,只需抛出async
中的异常。在有关异步操作的更复杂的情况下,在返回的任务中设置例外,如Foo1Async
此答案假设您指的是Foo2Async
返回未标有Task
的方法。在那些你无法控制正在创建的任务的人中,任何异常都会自动存储在该任务中(所以这个问题就无关紧要了。)