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();