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