簡體   English   中英

返回 Task 的方法應該拋出異常嗎?

[英]Should methods that return Task throw exceptions?

返回Task的方法有兩個報告錯誤的選項:

  1. 立即拋出異常
  2. 返回將以異常結束的任務

調用者是否應該期望兩種類型的錯誤報告,或者是否有一些標准/協議將任務行為限制為第二個選項?

例子:

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

處理這兩種類型看起來很丑:

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

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

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

使用 async/await 可能會有所幫助,但是對於沒有異步支持的普通 .NET 4 是否有解決方案?

大多數Task返回方法旨在與async / await一起使用(因此不應在內部使用Task.RunTask.Factory.StartNew )。

請注意,使用調用異步方法的常用方式,如何拋出異常並不重要:

await CheckWebPageAsync();

差異僅在調用該方法然后稍后等待時出現:

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

但是,通常調用( CheckWebPagesAsync() )和await位於同一代碼塊中,因此無論如何它們都將位於同一try / catch塊中,在這種情況下(通常)也無關緊要。

是否有一些標准/協議將任務行為限制為第二個選項?

沒有標准。 前置條件是一種愚蠢的異常,所以它是如何拋出的並不重要,因為它不應該被捕獲

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

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

請記住,永遠不應該有任何代碼捕獲 preconditions ,因此在現實世界中,拋出異常的方式不應該有任何區別。

另一方面,這我實際上不同意 Jon Skeet 的一點。 所以你的里程可能會有所不同......很多。 :)

我有非常相似的問題/疑問。 我試圖實現在接口中指定的異步方法(例如public Task DoSomethingAsync() )。 換句話說,接口期望特定函數( DoSomething )是異步的。

然而,事實證明該實現可以同步完成(而且我認為它的方法也不會花費很長時間才能完成)。

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

現在有四種方法可以做到這一點。

方法一:

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

方法二:

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
}

方法三:

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

方法四:

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

就像斯蒂芬克利里提到的那樣,所有這些通常都有效。 但是,存在一些差異。

  • 方法 1 要求您同步捕獲異常(在調用方法時而不是在等待時)。 如果您使用延續( task.ContinueWith(task => {}) )來處理異常,延續將根本不會運行。 這類似於您問題中的示例。
  • 方法 2 實際上效果很好,但您將不得不接受警告或插入#pragma抑制。 該方法最終可能會異步運行,從而導致不必要的上下文切換。
  • 方法 3 似乎是最直觀的。 但是有一個副作用 - stacktrace 根本不顯示DoSomethingAsync() 你所能看到的只是來電者。 這可能相當糟糕,具體取決於您拋出了多少相同類型的異常。
  • 方法四和方法二類似,可以await + catch異常; 你可以做任務延續; 堆棧跟蹤沒有丟失的信息。 它也是同步運行的,這對於非常輕量/快速的方法很有用。 但是......對於您實​​現的每種方法都以這種方式編寫它是非常尷尬的。

請注意,我是從實現的角度討論這個問題——我無法控制其他人如何調用我的方法。 目的是以正常執行的方式實現,獨立於調用方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM