C# 在维护wait时从'await'捕获异常,并返回外部作用域

C# 在维护wait时从'await'捕获异常,并返回外部作用域,c#,asynchronous,async-await,C#,Asynchronous,Async Await,我有一个很好的线性代码,由asyncawait启用,它返回一个值。我的大部分代码遵循以下模式: var a=wait Foo1(); Bar1(a); Bar2(a); Bar3(a); var b=等待Foo2(a); Bar4(a,b); 但是接下来我需要从async函数捕获异常 试试看 { var a=等待Foo1(); } 抓住(我的例外) { 扔我; } Bar1(a); Bar2(a); Bar3(a); 尝试 { var b=等待Foo2(a); } 捕获(MyException

我有一个很好的线性代码,由
async
await
启用,它返回一个值。我的大部分代码遵循以下模式:

var a=wait Foo1();
Bar1(a);
Bar2(a);
Bar3(a);
var b=等待Foo2(a);
Bar4(a,b);
但是接下来我需要从
async
函数捕获异常

试试看
{
var a=等待Foo1();
}
抓住(我的例外)
{
扔我;
}
Bar1(a);
Bar2(a);
Bar3(a);
尝试
{
var b=等待Foo2(a);
}
捕获(MyException2 me2)
{
投掷me2;
}
Bar4(a,b);
这将限定
try
中的变量的范围,现在它是一个编译错误。如果我想维护线性代码,那么
try
必须覆盖太多行,我认为这不是一个好的做法?现在它创建了一个缩进金字塔:

试试看
{
var a=等待Foo1();
Bar1(a);
Bar2(a);
Bar3(a);
尝试
{
var b=等待Foo2(a);
Bar4(a,b);
}
捕获(MyException2 me2)
{
投掷me2;
}
}
抓住(我的例外)
{
扔我;
}
因此,为了
try
只保留该行并维护线性代码,我需要首先将声明移动为nothing,然后在
try
中赋值,以维护外部范围上的变量

A;
尝试
{
a=等待Foo1();
}
抓住(我的例外)
{
扔我;
}
Bar1(a);
Bar2(a);
Bar3(a);
B B;
尝试
{
b=等待食物2(a);
}
捕获(MyException2 me2)
{
投掷me2;
}
Bar4(a,b);

现在它有点线性了。我也必须停止使用
var
,因为我无法再从
async
方法中推断。我觉得C#会提供一个我所缺少的更优雅的解决方案吗?

我想你可以这样做

public struct Result<TResult>
{
    public static Result<TResult> Ok(TResult data) => new Result<TResult>(data, true);
    public static Result<TResult> Error() => new Result<TResult>(default(TResult), false);

    private Result(TResult data, bool success)
    {
        Data = data;
        Success = success;
    }

    public bool Success { get; }
    public TResult Data { get; }
}

public static class TaskExt
{
    public static async Task<Result<T>> AwaitSafe<T, TException>(this Task<T> task, Action<TException> handle)
        where TException : Exception
    {
        var result = Result<T>.Error();
        try
        {
            result = Result<T>.Ok(await task);
        }
        catch (TException ex)
        {
            handle.Invoke(ex);
        }

        return result;
    }
}
只需在
try
之前声明变量(不进行设置)

还有,关于
抓住我

  • 如果您的整个
    catch
    块只想重新播放它,那么
    catch
    就没有意义了——只是根本就不播放它。但我猜你可能只是省略了那个代码
  • 如果您需要在
    catch
    中执行某些操作(比如将异常记录在某个地方),然后重新调用它,只需使用
    throw。这将在不更改堆栈跟踪的情况下重新显示异常-这很重要!
    
    • 如果你用
      扔我,堆栈跟踪将显示异常发生在
      throw me
    • 如果你只使用
      throw,堆栈跟踪将显示异常发生在它实际执行的行上(例如,
      wait Foo1();
      )。那更好™. 关于这一点,还有更多的阅读
以下是所有这些建议:

object a; //use the actual type
try
{
    a = await Foo1();
}
catch(MyException me)
{
    //do something else (or else just don't catch)
    throw;
}
Bar1(a);
Bar2(a);
Bar3(a);

object b; //use the actual type
try
{
    b = await Foo2(a);
}
catch(MyException2 me2)
{
    //do something else (or else just don't catch)
    throw;
}
Bar4(a,b);

为什么不将所有需要在
try
中声明变量的代码都放在
try

try
{
    var a = await Foo1();
    Bar1(a);
    Bar2(a);
    Bar3(a);
}
catch(MyException me)
{
    // Use `me` or log the event
    throw me;
}

try
{
    var b = await Foo2(a);
    Bar4(a,b);
}
catch(MyException2 me2)
{
    // Use `me2` or log the event
    throw me2;
}

这个答案的灵感来自于。通过使用方法安全地等待
任务
,然后查询其属性以确定访问任务的
结果
是否安全,可以避免try-catch块

//var a = await Foo1();
var task1 = await Task.WhenAny(Foo1());
if (task1.IsFaulted) return; // Or do something else
var a = task1.Result;

Bar1(a);
Bar2(a);
Bar3(a);

//var b = await Foo2(a);
var task2 = await Task.WhenAny(Foo2(a));
if (task2.IsFaulted) return; // Or do something else
var b = task2.Result;

Bar4(a, b);
如果您认为使用
任务。当任何
进行安全等待都违反此方法的目的时,您可以使用下面的扩展方法:

public static async Task<Task<T>> AwaitSafe<T>(this Task<T> source)
{
    try
    {
        await source.ConfigureAwait(false);
    }
    catch { }
    return source;
}

显然,您需要做的不是
抛出我(2)
,因为您也可以忽略这一点,同样的事情也会发生(使用更好的堆栈跟踪)。请回答您的问题,以显示您实际想要在那里执行的操作。使用
AwaitSafe
如何知道是否发生异常,以有条件地从不同方向继续?我看到的唯一方法是更新
句柄
中的局部变量,这很尴尬。如果要停止进一步执行,只需
抛出
。如果您不想抛出-您应该使用
default(t)
作为某个错误的标记。如果它仍然不明显,你可以使用
也许
或者
结果
单子,那么如果
默认值(T)
恰好是一个有效的返回值,那么另一种选择是深入研究单子的理论?@TheodorZoulias你觉得我更新的答案怎么样?现在更好了。此方法可能是可用的。:-)
如果您的整个catch块只想重新调用它,那么catch就没有任何意义了——根本就没有捕获它。
一个迂腐的参数就是能够设置断点(这类似于
var result=Foo();return result;
return Foo();
,这是一个容易突破的问题)但是我确实同意非调试情况。捕获有一个目的:)但是,我只是将VisualStudio调试选项设置为在所有异常(而不仅仅是未处理的异常)上中断。这也有助于发现可能被错误吞没的异常,否则您将永远看不到这些异常。
public static async Task<Task<T>> AwaitSafe<T>(this Task<T> source)
{
    try
    {
        await source.ConfigureAwait(false);
    }
    catch { }
    return source;
}
var task1 = await Foo1().AwaitSafe();