异步方法如何在不抛出异常的情况下失败

3
假设我有以下方法,以及调用它的代码:
public async Task<MyResult> PerformAction(string parameter)
{
   if(parameter == "fail")
      throw new Exception("You wanted me to fail.");
   return await MyResult.Create(parameter);
}

var resultOne = await PerformAction("fail");
var resultTwo = await PerformAction("success");

这种方法可以正常工作,但会抛出异常,导致性能下降。

有没有一种方法可以告诉调用者任务失败了,但不需要抛出异常呢?


你可以在 MyResult 中包含一个 bool IsSuccess 属性。 - aepot
你在PerformAction上缺少了async - Guru Stron
2
顺便提一下,异常是不应该表示操作结果(即使是坏的结果)而是失败的东西。如果代码干净且正确实现,则应尽可能少地抛出异常。在完美的情况下,环境适合应用程序的需求,就不会抛出任何异常。Exception收集堆栈跟踪,这是一个缓慢而繁重的操作。 - aepot
@GuruStron - 谢谢,我已经编辑了帖子。 - Mike Christiansen
2个回答

3

在您的情况下,您可以执行以下操作:

public Task<MyResult> PerformAction(string parameter)
{
   if(parameter == "fail")
   {
      return Task.FromException<MyResult>(new Exception("You wanted me to fail."));
   }
   return MyResult.Create(parameter);
}

不确定在性能方面会快多少。


1
我认为Exception的性能不会有变化,但是你成功地消除了一个async状态机,这对性能是有好处的。 - aepot
2
否则它将无法编译,应该是 FromException<MyResult> - user10608418
@aepot 是的,我也会这样假设,但还没有检查过。 - Guru Stron
2
另一个性能提示:ValueTask - aepot
@aepot ValueTask 没有 Exception 属性,因此如果要在没有状态机的情况下传播失败,则仍需包装一个 Task。但是,如果异常的类型和消息始终相同,则可以缓存该 Task - Theodor Zoulias
@TheodorZoulias 正确。那是关于性能而非异常的问题。关于异常,请参阅我在问题帖子下的评论。 - aepot

1

当可能时,避免生成和传播异常的常见解决方案是将结果包装在外部对象中:

public class Response<T> {
   public IEnumerable<string> Errors {get;set;}
   public bool HasErrors => Errors?.Any() ?? false;
   public bool IsSuccess {get;set;}
   public T Data {get;set;}
}

public async Task<Response<MyResult>> PerformAction(string parameter)
{
   if(parameter == "fail")
      return new Reponse<MyResult>() {
          IsSuccess = false,
          Errors = new string[] {"Failed"}
      };
   return new Response<MyResult>(){
             Result = await MyResult.Create(parameter).ConfigureAwait(false),
             IsSuccess = true
           };
}

var resultOne = await PerformAction("fail");
if (resultOne.HasErrors) { ... }
var resultTwo = await PerformAction("success");
if (resultTwo.HasErrors) { ... }

null不是有效结果时,我可以使用null来指示失败。但是,当null是一个有效结果时,它不能用来指示失败。在这种情况下,我可能会倾向于返回一个包装类型或元组。然而...我希望有一种更"清晰"的方法来解决这个问题。 - Mike Christiansen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接