返回Task
的方法有两个报告错误的选项:
调用者是否应该期望两种类型的错误报告,或者是否有某些标准/协议将任务行为限制为第二个选项?
示例:
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");
});
}
}
处理这两种类型看起来很丑陋:
try {
var task = pageChecker.CheckWebPage(url);
task.ContinueWith(t =>
{
if(t.Exception!=null)
ReportBadPage(url);
});
}
catch(Exception ex) {
ReportBadPage(url);
}
使用async / await可能有所帮助,但有没有异步支持的纯.NET 4解决方案吗?
答案 0 :(得分:12)
大多数Task
- 返回方法适用于async
/ await
(因此不应在内部使用Task.Run
或Task.Factory.StartNew
。< / p>
请注意,使用调用异步方法的常用方法,抛出异常的方式并不重要:
await CheckWebPageAsync();
差异只发生在调用方法然后等待的时候:
List<Task> tasks = ...;
tasks.Add(CheckWebPagesAsync());
...
await Task.WhenAll(tasks);
但是,通常调用(CheckWebPagesAsync()
)和await
位于相同的代码块中,因此无论如何它们都位于相同的try
/ catch
块中,在这种情况下,它(通常)也无关紧要。
是否有一些标准/协议将任务行为限制为第二个选项?
没有标准。前置条件是boneheaded exception的一种类型,所以抛出它并不重要,因为它永远不会被捕获。
Jon Skeet认为应该直接抛出先决条件(&#34;外部&#34;返回的任务):
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运算符提供了一个很好的并行,它们可以保证抛出异常&#34;早期&#34;像这样(在调查员之外)。
但我不认为这是必要的。在任务中抛出前置条件时,我发现代码更简单:
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");
}
请记住,应该永远不会有任何捕获前置条件的代码,因此在现实世界中,抛出异常的方式不应该有任何区别。
另一方面,这个 是我实际上不同意Jon Skeet的一点。所以你的里程可能会有所不同......很多。 :)
答案 1 :(得分:0)
我有非常相似的问题/疑问。我正在尝试实现在接口中指定的异步方法(例如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);
}
}
}
现在我有四种方法可以做到这一点。
方法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);
}
}
方法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
}
方法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;
}
}
方法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;
}
}
就像史蒂芬·克雷里(Stephen Cleary)提到的那样,所有这些通常都能奏效。但是,还是有一些区别。
task.ContinueWith(task => {})
)处理异常,则延续将根本无法运行。这类似于您问题中的示例。#pragma
禁止内容。该方法可能最终异步运行,从而导致不必要的上下文切换。DoSomethingAsync()
!您所看到的只是呼叫者。取决于您要抛出多少个相同类型的异常,这可能会很糟糕。await
+ catch
例外;您可以执行任务继续; stacktrace没有丢失的信息。它也同步运行,这对于非常轻量/快速的方法很有用。但是...对于您实现的每种方法,以这种方式编写它都非常尴尬。请注意,我正在从实现的角度进行讨论-我无法控制其他人如何调用我的方法。目的是以一种正常执行的方式实现,而与调用方法无关。