C# 处理异步方法的同步部分的异常
我正在处理这样一种情况:我开始的任务可能会抛出,同时仍然在初始线程上同步执行。出于说明目的,类似于此:C# 处理异步方法的同步部分的异常,c#,.net,exception,task-parallel-library,async-await,C#,.net,Exception,Task Parallel Library,Async Await,我正在处理这样一种情况:我开始的任务可能会抛出,同时仍然在初始线程上同步执行。出于说明目的,类似于此: static async Task TestAsync() { var random = new Random(Environment.TickCount).Next(); if (random % 2 != 0) throw new ApplicationException("1st"); await Task.Delay(2000); Co
static async Task TestAsync()
{
var random = new Random(Environment.TickCount).Next();
if (random % 2 != 0)
throw new ApplicationException("1st");
await Task.Delay(2000);
Console.WriteLine("after await Task.Delay");
throw new ApplicationException("2nd");
}
从调用代码中,我希望能够捕获任何异常,可能是从同步部分抛出的(即,直到wait Task.Delay()
)。以下是我目前的做法:
static void Main(string[] args)
{
try
{
var task = TestAsync();
if (task.IsFaulted)
task.GetAwaiter().GetResult();
Console.WriteLine("TestAsync continues asynchronously...");
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.ToString());
}
Console.WriteLine("Press Enter to exit...");
Console.ReadLine();
}
这是可行的,尽管它看起来有点口香糖,因为任务上没有结果
我还尝试了task.Wait()
而不是task.GetAwaiter().GetResult()
。这总是给我AggregateException
,我必须打开它(而不是直接打开ApplicationException
)
还有其他选择吗
[编辑]处理评论:我这样做是因为如果任务立即失败,我不想将其添加到我维护的挂起任务列表中。任务本身对这样一个列表一无所知(也不必知道)。我仍然希望记录异常,并让用户知道它。我还可以执行抛出task.Exception
,但这不会给出用ExceptionDispatchInfo
捕获的异常堆栈帧
[UPDATE]受到其他答案和评论的启发:如果我完全控制TestAsync
,并且我不想引入新的类成员,我也可以做如下操作。验证参数时,它可能很方便:
static Task TestAsync(int delay)
{
if (delay < 0)
throw new ArgumentOutOfRangeException("delay");
Func<Task> asyncPart = async () =>
{
Console.WriteLine("await Task.Delay");
await Task.Delay(delay);
throw new ApplicationException("2nd");
};
return asyncPart();
}
静态任务测试同步(整数延迟)
{
如果(延迟<0)
抛出新ArgumentOutOfRangeException(“延迟”);
Func asyncPart=async()=>
{
Console.WriteLine(“wait Task.Delay”);
等待任务。延迟(延迟);
抛出新的ApplicationException(“第二个”);
};
返回asyncPart();
}
我将它分为两部分,而不是依靠task.GetAwaiter().GetResult()
来工作。我担心维护TestAsync
的人将来可能会无意中破坏一些东西
我就是这样写的。这应该会保留你的行为,但我发现更明显的是发生了什么:
static Task Test()
{
var random = new Random(Environment.TickCount).Next();
if (random % 2 != 0)
throw new ApplicationException("1st");
return TestAsync();
}
static async Task TestAsync()
{
await Task.Delay(2000);
Console.WriteLine("after await Task.Delay");
throw new ApplicationException("2nd");
}
static void Main(string[] args)
{
try
{
Test();
Console.WriteLine("TestAsync continues asynchronously...");
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.ToString());
}
Console.WriteLine("Press Enter to exit...");
Console.ReadLine();
}
一般来说,异常不应用于应用程序中的常规错误处理。在“异常”情况下抛出异常,在这种情况下,程序无法继续,因为发生了意外事件,您需要执行硬停止
当然,我不知道您的用例是什么,但每当我使用异步任务时,意外失败的部分通常也是应该异步的部分(例如连接到数据库)
无论如何,我将如何将您的TestAsync
方法放入它自己的类中。然后,您可以使用一个方法(或属性)bool TestAsync.IsValid
来确定任务是否准备就绪,是否应该排队等待执行;然后,如果答案为true,则可以运行异步任务:TestAsync.RunAsync()
我认为没有办法做到这一点。。。我相信编译器会将您的TestAsync
函数重写为一个状态机类。。。如果您使用类似ILSPY的东西反编译EXE,您可以看到。采用异步方法并同步运行它与运行常规同步方法不同。@JohnGibb,不幸的是,除了我提到的这两种方法之外,你的意思是没有其他方法可以做到这一点?我的意思是没有办法让它抛出基础异常而不是聚合异常。任务可以强制同步运行,但不会有相同的异常抛出行为。@nosertio:我认为您的解决方案是最好的,除非您将所有TestAsync
方法拆分为task TestAsync(){/*do可以抛出的东西*/return TestInternalAsync();}async task TestInternalAsync(){/*异步部分*/}
@nosratio:您可以使用这样的嵌套lambda。在我看来,与单独的方法相比,它确实降低了代码的可读性,但它应该工作得很好。这可能是一种解决方法,我承认。但是,在另一种情况下,TestAsync
可能由我无法控制的库实现。如果它是另一个库,我会重新考虑对异步函数的实现做出这样的假设。如果同步部分在某些情况下需要很长时间怎么办?如果它非常快,然后异步部分在0.001秒后发生错误怎么办?为什么不在第一秒钟检查错误或其他什么,而不关心“同步部分”呢?这些都是相关的问题。我有观察列表中已经存在的后台任务完成情况的逻辑。但是,如果任务在启动异步IO绑定操作之前失败(例如,因为找不到文件),我希望避免将任务添加到列表中。如果同步部分在某些情况下需要很长时间怎么办?在这种情况下,我的调用线程将被阻塞(如果我从UI线程调用TestAsync
,UI也会被阻塞)。我必须求助于类似Task.Run(()=>TestAsync)的解决方法
,如果我无法避免使用这样一个设计糟糕的库的话。第二个异常将永远不会被捕获,因为任务的结果不会在任何地方使用-因此,即使random
为2,也不会将“错误”消息打印到屏幕上。您需要使用Test().Wait()
来引发第二个异常(作为聚合异常
)检查预条件怎么样,比如参数值?这在早期是很常见的事情。Chris,我不相信抛出异常来检查预条件。这些可以用if语句来检查。除非是API,在这种情况下你需要抛出-但我怀疑这是其中的一种情况。我不确定我是否理解。你显然明白使用if
语句检查参数值是否有效。如果参数值无效,您会怎么做?@r