C# 实现返回任务的方法时的合同协议

C# 实现返回任务的方法时的合同协议,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

在实现返回与抛出异常有关的任务的方法时,是否有MS“最佳实践”或合同协议?这是在编写单元测试时出现的,我试图弄清楚是否应该测试/处理这种情况(我知道答案可能是“防御性编码”,但我不希望这是答案)

i、 e

  • 方法必须始终返回任务,该任务应包含引发的异常

  • 方法必须始终返回任务,除非该方法提供无效参数(即ArgumentException)

  • 方法必须始终返回一个任务,除非开发人员流氓并做他/她想做的事情(jk)

  • 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);
    }