[英]Should methods that return Task throw exceptions?
The methods that return Task
have two options for reporting an error:返回Task
的方法有两个报告错误的选项:
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.Run
或Task.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.但是,存在一些差异。
task.ContinueWith(task => {})
) to handle exception, the continuation would simply not run.如果您使用延续( task.ContinueWith(task => {})
)来处理异常,延续将根本不会运行。 This is similar to the example in your question.这类似于您问题中的示例。#pragma
suppressions.方法 2 实际上效果很好,但您将不得不接受警告或插入#pragma
抑制。 The method could end up running asynchronously, causing unnecessary context switches.该方法最终可能会异步运行,从而导致不必要的上下文切换。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.这可能相当糟糕,具体取决于您抛出了多少相同类型的异常。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.