C# 实现返回任务的方法时的合同协议
在实现返回与抛出异常有关的任务的方法时,是否有MS“最佳实践”或合同协议?这是在编写单元测试时出现的,我试图弄清楚是否应该测试/处理这种情况(我知道答案可能是“防御性编码”,但我不希望这是答案) i、 eC# 实现返回任务的方法时的合同协议,c#,.net,exception,task-parallel-library,async-await,C#,.net,Exception,Task Parallel Library,Async Await,在实现返回与抛出异常有关的任务的方法时,是否有MS“最佳实践”或合同协议?这是在编写单元测试时出现的,我试图弄清楚是否应该测试/处理这种情况(我知道答案可能是“防御性编码”,但我不希望这是答案) i、 e 方法必须始终返回任务,该任务应包含引发的异常 方法必须始终返回任务,除非该方法提供无效参数(即ArgumentException) 方法必须始终返回一个任务,除非开发人员流氓并做他/她想做的事情(jk) Task Foo1Async(字符串id){ if(id==null){ 抛出新Argum
Task Foo1Async(字符串id){
if(id==null){
抛出新ArgumentNullException();
}
//做事
}
任务同步(字符串id){
if(id==null){
var source=new TaskCompletionSource();
SetException(新ArgumentNullException());
返回源任务;
}
//做事
}
任务栏(字符串id){
//参数检查
如果(id==null)抛出新的ArgumentNullException(“id”)
试一试{
返回此.SomeService.GetAsync(id).ContinueWith(t=>{
//在此检查故障状态
//传递异常。
})
}捕获(例外情况除外){
//在这里处理更多的故障状态。
//防御代码。
//返回带有异常的任务。
var source=new TaskCompletionSource();
source.SetException(ex);
返回源任务;
}
}
方法返回任务的一般情况是因为它们是异步方法。在这些情况下,方法的同步部分中的异常应该像在任何其他方法中一样抛出,而异步部分中的异常应该存储在返回的任务
中(通过调用异步
方法或匿名委托自动进行)
因此,在一些简单的情况下,如无效参数,只需抛出一个异常,如Foo1Async
。在异步操作的更复杂情况下,在返回的任务上设置异常,如Foo2Async
这个答案假设您所指的是
任务
返回的方法没有标记为异步
。在这些情况下,您无法控制正在创建的任务,并且任何异常都会自动存储在该任务中(因此该问题无关紧要)。最近我问了一个类似的问题:
如果该方法有一个async
签名,那么从方法的同步或异步部分抛出该签名并不重要。在这两种情况下,异常都将存储在任务中。唯一的区别是,在前一种情况下,生成的任务
对象将立即完成(出现故障)
如果该方法没有async
签名,则可能会在调用方的堆栈帧上引发异常
在这两种情况下,调用方都不应该假设异常是从同步部分还是异步部分抛出的,或者方法是否具有async
签名
如果您确实需要知道任务是否已同步完成,您可以随时检查其任务。已完成
/故障
/已取消
状态或任务。异常
属性,而无需等待:
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;
}
这也适用于单元测试,只要async
方法返回任务
(单元测试引擎不支持async void
方法,AFAIK,这很有意义:没有任务
来跟踪和等待
)
回到你的代码,我这样说:
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
的调用者处理异常,而不是手动捕获和传播异常。我知道Jon Skeet非常喜欢用单独的同步方法进行前置条件样式的检查,以便直接抛出异常
然而,我自己的观点是“没关系”。想想Eric Lippert的。我们都同意,外部异常应该放在返回的任务上(而不是直接抛出到调用方的堆栈框架上)。应该完全避免令人烦恼的例外情况。唯一有争议的异常类型是bonehead异常(例如,参数异常)
我的论点是,它们如何抛出并不重要,因为您不应该编写捕获它们的生产代码。您的单元测试是唯一应该捕获ArgumentException
和friends的代码,如果您使用await
,则抛出它们的时间无关紧要。有趣的问题。我从未见过这样的指导原则,所以我想说4:方法可能抛出异常或返回包含该异常的任务,调用方应将这些情况视为等效情况。@hvd防御性编码建议调用方处理这两种情况,但我的极简主义者不想处理以多种方式表达的异常。异常将是参数检查(有点自相矛盾)。但同时,异常抛出的方式不应取决于方法是否使用async
实现,因为这是调用方看不到的实现细节。如果您同意这一点,那么要么将这两种情况视为等价的,要么永远不要同步抛出异常。+1。同意。像参数验证这样的东西没有理由等待。如果不能立即抛出,则将其放入任务中。我使用Bar(string):task更新了示例。Bar依赖于某个返回任务的服务。该示例应在两个区域处理异常/故障状态(试块和继续)。@Kevin yes。我就是这么想的meant@l3arnon:在这些情况下,除了
try
{
var result = await Foo1Async(id);
}
catch (Exception ex)
{
// handle it
Debug.Print(ex.ToString());
}
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);
}