简体   繁体   English

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

[英]Should methods that return Task throw exceptions?

The methods that return Task have two options for reporting an error:返回Task的方法有两个报告错误的选项:

  1. throwing exception right away立即抛出异常
  2. returning the task that will finish with the exception返回将以异常结束的任务

Should the caller expect both types of error reporting or is there some standard/agreement that limits task behavior to the second option?调用者是否应该期望两种类型的错误报告,或者是否有一些标准/协议将任务行为限制为第二个选项?

Example:例子:

class PageChecker 
{
    Task CheckWebPage(string url) 
    {
        if(url == null) // Argument check
            throw Exception("Bad URL");
            
        // Some other synchronous check
        if(!HostPinger.IsHostOnline(url)) 
            throw Exception("Host is down");

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

Handling both types looks pretty ugly:处理这两种类型看起来很丑:

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

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

}
catch(Exception ex) 
{
    ReportBadPage(url);
}

Using async/await may help, but is there a solution for plain .NET 4 without asynchronous support?使用 async/await 可能会有所帮助,但是对于没有异步支持的普通 .NET 4 是否有解决方案?

Most Task -returning methods are intended for use with async / await (and as such should not use Task.Run or Task.Factory.StartNew internally).大多数Task返回方法旨在与async / await一起使用(因此不应在内部使用Task.RunTask.Factory.StartNew )。

Note that with the common way of calling asynchronous methods, it doesn't matter how the exception is thrown:请注意,使用调用异步方法的常用方式,如何抛出异常并不重要:

await CheckWebPageAsync();

The difference only comes in when the method is called and then awaited later:差异仅在调用该方法然后稍后等待时出现:

List<Task> tasks = ...;
tasks.Add(CheckWebPagesAsync());
...
await Task.WhenAll(tasks);

However, usually the call ( CheckWebPagesAsync() ) and the await are in the same block of code, so they would be in the same try / catch block anyway, and in that case it also (usually) doesn't matter.但是,通常调用( CheckWebPagesAsync() )和await位于同一代码块中,因此无论如何它们都将位于同一try / catch块中,在这种情况下(通常)也无关紧要。

is there some standard/agreement that limits task behavior to the second option?是否有一些标准/协议将任务行为限制为第二个选项?

There is no standard.没有标准。 Preconditions are a type of boneheaded exception , so it doesn't really matter how it's thrown because it should never be caught anyway.前置条件是一种愚蠢的异常,所以它是如何抛出的并不重要,因为它不应该被捕获

Jon Skeet is of the opinion that preconditions should be thrown directly ("outside" the returned task): 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");
}

This provides a nice parallel to LINQ operators, which are guaranteed to throw exceptions "early" like this (outside the enumerator).这为 LINQ 运算符提供了一个很好的并行,保证像这样(在枚举器之外)“提前”抛出异常。

But I don't think that's necessary.但我不认为这是必要的。 I find the code is simpler when throwing preconditions within the task:我发现在任务中抛出先决条件时代码更简单:

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

Remember, there should never be any code that catches preconditions , so in the real world, it shouldn't make any difference how the exception is thrown.请记住,永远不应该有任何代码捕获 preconditions ,因此在现实世界中,抛出异常的方式不应该有任何区别。

On the other hand, this is one point where I actually disagree with Jon Skeet.另一方面,这我实际上不同意 Jon Skeet 的一点。 So your mileage may vary... a lot.所以你的里程可能会有所不同......很多。 :) :)

I was having very similar issue/doubts.我有非常相似的问题/疑问。 I was trying to implement asynchronous methods (eg public Task DoSomethingAsync() ) which was specified in an interface.我试图实现在接口中指定的异步方法(例如public Task DoSomethingAsync() )。 To rephrase, the interface expects the particular function ( DoSomething ) to be asynchronous.换句话说,接口期望特定函数( DoSomething )是异步的。

However, it turns out that the implementation could do it synchronously (and I don't think it method is going to take very long to complete either).然而,事实证明该实现可以同步完成(而且我认为它的方法也不会花费很长时间才能完成)。

public interface IFoobar
{
    Task DoSomethingAsync(Foo foo, Bar bar);
}

public class Caller
{
    public async void Test
    {
        try
        {
            await new Implementation().DoSomethingAsync(null, null);
        }
        catch (Exception e)
        {
            Logger.Error(e);
        }
    }
}

Now there are four ways that I could do this.现在有四种方法可以做到这一点。

Method 1:方法一:

public class Implementation : IFoobar
{
    public Task DoSomethingAsync(Foo foo, Bar bar)
    {
        if (foo == null)
            throw new ArgumentNullException(nameof(foo));
        if (bar == null)
            throw new ArgumentNullException(nameof(bar));

        DoSomethingWithFoobar(foo, bar);
    }
}

Method 2:方法二:

public class Implementation : IFoobar
{
    #pragma warning disable 1998
    public async Task DoSomethingAsync(Foo foo, Bar bar)
    {
        if (foo == null)
            throw new ArgumentNullException(nameof(foo));
        if (bar == null)
            throw new ArgumentNullException(nameof(bar));

        DoSomethingWithFoobar(foo, bar);
    }
    #pragma warning restore 1998
}

Method 3:方法三:

public class Implementation : IFoobar
{
    public Task DoSomethingAsync(Foo foo, Bar bar)
    {
        if (foo == null)
            return Task.FromException(new ArgumentNullException(nameof(foo)));
        if (bar == null)
            return Task.FromException(new ArgumentNullException(nameof(bar)));

        DoSomethingWithFoobar(foo, bar);
        return Task.CompletedTask;
    }
}

Method 4:方法四:

public class Implementation : IFoobar
{
    public Task DoSomethingAsync(Foo foo, Bar bar)
    {
        try
        {
            if (foo == null)
                throw new ArgumentNullException(nameof(foo));
            if (bar == null)
                throw new ArgumentNullException(nameof(bar));
        }
        catch (Exception e)
        {
            return Task.FromException(e);
        }

        DoSomethingWithFoobar(foo, bar);
        return Task.CompletedTask;
    }
}

Like what Stephen Cleary has mentioned, all of these generally works.就像斯蒂芬克利里提到的那样,所有这些通常都有效。 However, there are some differences.但是,存在一些差异。

  • Method 1 requires you to catch the exception synchronously (when calling method rather than when awaiting).方法 1 要求您同步捕获异常(在调用方法时而不是在等待时)。 If you used a continuation ( task.ContinueWith(task => {}) ) to handle exception, the continuation would simply not run.如果您使用延续( task.ContinueWith(task => {}) )来处理异常,延续将根本不会运行。 This is similar to the example in your question.这类似于您问题中的示例。
  • Method 2 actually works very well, but you will have to live with the warning or insert the #pragma suppressions.方法 2 实际上效果很好,但您将不得不接受警告或插入#pragma抑制。 The method could end up running asynchronously, causing unnecessary context switches.该方法最终可能会异步运行,从而导致不必要的上下文切换。
  • Method 3 seems the most intuitive.方法 3 似乎是最直观的。 There is one side-effect though - stacktrace does not show DoSomethingAsync() at all!但是有一个副作用 - stacktrace 根本不显示DoSomethingAsync() All you could see is the caller.你所能看到的只是来电者。 This could be rather bad depending on how many exceptions of the same type you are throwing.这可能相当糟糕,具体取决于您抛出了多少相同类型的异常。
  • Method 4 is similar to method 2. You could await + catch the exception;方法四和方法二类似,可以await + catch异常; you could do task continuations;你可以做任务延续; the stacktrace doesn't have missing information.堆栈跟踪没有丢失的信息。 It's also running synchronously, which can be useful for very lightweight/quick methods.它也是同步运行的,这对于非常轻量/快速的方法很有用。 But... it's awfully awkward to write it this way for every method you implement.但是......对于您实​​现的每种方法都以这种方式编写它是非常尴尬的。

Note that I'm discussing this from the implementation's point of view - I have no control how someone else could call my method.请注意,我是从实现的角度讨论这个问题——我无法控制其他人如何调用我的方法。 The aim is to implement in a way that executes normally, independent on the method of calling.目的是以正常执行的方式实现,独立于调用方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM