[英]Should methods that return Task throw exceptions?
返回Task
的方法有兩個報告錯誤的選項:
調用者是否應該期望兩種類型的錯誤報告,或者是否有一些標准/協議將任務行為限制為第二個選項?
例子:
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.Run
或Task.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;
}
}
就像斯蒂芬克利里提到的那樣,所有這些通常都有效。 但是,存在一些差異。
task.ContinueWith(task => {})
)來處理異常,延續將根本不會運行。 這類似於您問題中的示例。#pragma
抑制。 該方法最終可能會異步運行,從而導致不必要的上下文切換。DoSomethingAsync()
! 你所能看到的只是來電者。 這可能相當糟糕,具體取決於您拋出了多少相同類型的異常。await
+ catch
異常; 你可以做任務延續; 堆棧跟蹤沒有丟失的信息。 它也是同步運行的,這對於非常輕量/快速的方法很有用。 但是......對於您實現的每種方法都以這種方式編寫它是非常尷尬的。請注意,我是從實現的角度討論這個問題——我無法控制其他人如何調用我的方法。 目的是以正常執行的方式實現,獨立於調用方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.