应该返回Task的方法抛出异常吗?

ale*_*lex 13 .net c# asynchronous .net-4.0 task-parallel-library

返回的方法Task有两个报告错误的选项:

  1. 立刻抛出异常
  2. 返回将以异常结束的任务

调用者是否应该期望两种类型的错误报告,或者是否存在将任务行为限制为第二个选项的某些标准/协议?

例:

class PageChecker {
    Task CheckWebPage(string url) {
        if(url == null) // Argument check
            throw Exception("Bad URL");

        if(!HostPinger.IsHostOnline(url)) // Some other synchronous check
            throw Exception("Host is down");

        return Task.Factory.StartNew(()=> {
            // Asynchronous check
            if(PageDownloader.GetPageContent(url).Contains("error"))
                throw Exception("Error on the page");
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

处理这两种类型看起来很丑陋:

try {
    var task = pageChecker.CheckWebPage(url);

    task.ContinueWith(t =>
        {
            if(t.Exception!=null)
                ReportBadPage(url);
        });

}
catch(Exception ex) {
    ReportBadPage(url);
}
Run Code Online (Sandbox Code Playgroud)

使用async/await可能有所帮助,但有没有异步支持的纯.NET 4解决方案?

Ste*_*ary 12

大多数Task退回方法旨在与async/ 一起使用await(因此不应使用Task.RunTask.Factory.StartNew内部使用).

请注意,使用调用异步方法的常用方法,抛出异常的方式无关紧要:

await CheckWebPageAsync();
Run Code Online (Sandbox Code Playgroud)

差异只发生在调用方法然后等待的时候:

List<Task> tasks = ...;
tasks.Add(CheckWebPagesAsync());
...
await Task.WhenAll(tasks);
Run Code Online (Sandbox Code Playgroud)

但是,通常call(CheckWebPagesAsync())和它们await都在同一个代码块中,所以无论如何它们都在同一个try/ catchblock中,在这种情况下它也(通常)无关紧要.

是否有一些标准/协议将任务行为限制为第二种选择?

没有标准.前提条件是一种类型的骨头异常,所以它抛出的方式并不重要,因为它永远不会被捕获.

Jon Skeet认为应该直接抛出先决条件(返回任务的"外部"):

Task CheckWebPageAsync(string url) {
  if(url == null) // argument check            
    throw Exception("Bad url");                     

  return CheckWebPageInternalAsync(url);
}

private async Task CheckWebPageInternalAsync(string url) {
  if((await PageDownloader.GetPageContentAsync(url)).Contains("error")) 
    throw Exception("Error on the page");
}
Run Code Online (Sandbox Code Playgroud)

这为LINQ运算符提供了一个很好的并行,它保证像这样"早期"抛出异常(在枚举器之外).

但我不认为这是必要的.在任务中抛出前置条件时,我发现代码更简单:

async Task CheckWebPageAsync(string url) {
  if(url == null) // argument check            
    throw Exception("Bad url");                     

  if((await PageDownloader.GetPageContentAsync(url)).Contains("error")) 
    throw Exception("Error on the page");
}
Run Code Online (Sandbox Code Playgroud)

请记住,永远不应该有任何捕获前置条件的代码,因此在现实世界中,抛出异常的方式不应该有任何区别.

另一方面,这我实际上不同意Jon Skeet的一点.所以你的里程可能会有所不同......很多.:)

  • 斯蒂芬虽然肯定不是问题或答案的核心,但在代码示例中,指出应该抛出更具体的异常也是有用的.例如:`throw new ArgumentNullException("URL不能为null.","url")`和`抛出新的ArgumentException("页面上的错误.","url")`. (3认同)