在同步调用的异步代码中捕获异常

Catching exceptions in async code called synchronously

我有用于身份验证的 thrift 服务。 catch (AccountNotFoundException) 不会捕获异常,除非我在 Task.Run 中调用它。奇怪的是测试用例没问题。为什么?是因为 task.start() 与 catch 处于不同的级别吗?

    public override string GetUserNameByEmail(string email)
    {
         var task = client.GetUserByEmail(email, false);
         return task.Result;
         // I changed to
         // return Task.Run(() => client.GetUserByEmail(email, false)).Result.UserName;
         // and I was able to catch the exception
    }

    public async Task<AccountDetails> GetAccountDetailsByEmail(string email)
    {
        try
        {
            return await Call(() => client.getAccountDetailsByEmail(email));
        }
        catch (AccountNotFoundException)
        {
            return null;
        }
    }

    private async Task<T> Call<T>(Func<T> call)
    {
        try
        {
            transport.Open();
            var thriftTask = new Task<T>(call);
            thriftTask.Start();
            return await thriftTask;
        }
        catch (DatabaseException e)
        {
            Logger.Error(e);
            throw;
        }
        finally
        {
            transport.Close();
        }
    }

测试用例工作正常

    [TestMethod]
    public async Task Nonexisting_User_I_Expect_To_Be_Null()
    {
        var user = Provider.GetUser("idontexist@bar.com", false);
        Assert.IsNull(user);
    }

编辑:

我有以下理论为什么我的代码 运行 ok:代码能正常工作是因为我很幸运。请求和异步由同一线程处理,因此它共享相同的上下文,因此不会阻塞。

有关异步代码异常处理的详细信息,请参阅此处。可能是您正在捕获 AccountNotFoundException,而当您真的想捕获异常时,它将 InnerException 设置为 AccountNotFoundException:

https://msdn.microsoft.com/en-us/library/0yd65esw.aspx

节选:

任务的IsFaulted属性设置为True,任务的Exception.InnerException属性设置为异常,在catch块中捕获异常.

    public async Task DoSomethingAsync()
    {
        Task<string> theTask = DelayAsync();

        try
        {
            string result = await theTask;
            Debug.WriteLine("Result: " + result);
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Exception Message: " + ex.Message);
        }
        Debug.WriteLine("Task IsCanceled: " + theTask.IsCanceled);
        Debug.WriteLine("Task IsFaulted:  " + theTask.IsFaulted);
        if (theTask.Exception != null)
        {
            Debug.WriteLine("Task Exception Message: "
                + theTask.Exception.Message);
            Debug.WriteLine("Task Inner Exception Message: "
                + theTask.Exception.InnerException.Message);
        }
    }

    private async Task<string> DelayAsync()
    {
        await Task.Delay(100);

        // Uncomment each of the following lines to 
        // demonstrate exception handling. 

        //throw new OperationCanceledException("canceled");
        //throw new Exception("Something happened.");
        return "Done";
    }

首先,您不应该同步调用异步方法。正如我在我的博客中所描述的那样,approach you're using is prone to deadlocks.

您看到意外异常类型的原因是 Result 会将所有任务异常包装在 AggregateException 中。为避免这种情况,您可以调用 GetAwaiter().GetResult().

这与 Start 没有任何关系,但既然你提到了它,Start 成员实际上并没有用例。从来没有充分的理由使用它。相反,使用 Task.Run:

var thriftTask = Task.Run(call);