【问题标题】:Catching exceptions in async code called synchronously在同步调用的异步代码中捕获异常
【发布时间】:2015-04-26 19:27:02
【问题描述】:

我有验证服务。 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);
    }

编辑:

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

【问题讨论】:

    标签: c# .net exception asynchronous asp.net-membership


    【解决方案1】:

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

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

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

    var thriftTask = Task.Run(call);
    

    【讨论】:

    • 你能解释一下为什么 task.start 是个坏主意吗? dotnetcodr.com/2014/01/01/5-ways-to-start-a-task-in-net-c
    • 此外,代码在自定义成员资格提供程序中调用,该提供程序实现抽象方法,例如public override MembershipUser GetUser(string username, bool userIsOnline) 当我无法更改接口以使异步“一直向下”时如何调用它
    • @lukas: Task.Start 是不必要的; Task.Run 更短更清晰,并避免了有问题的任务状态 (see my blog)。最好的解决方案是更改您的自定义成员资格提供程序,以便它可以具有异步实现。
    • 问题是我需要重写抽象类方法,我无法将签名更改为异步。
    • 我明白你在说什么。我的意思是最好的方法是改变抽象类。
    【解决方案2】:

    有关异步代码异常处理的详细信息,请参见此处。可能是您正在捕获一个 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";
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-08
      • 1970-01-01
      • 2014-06-03
      • 2011-08-12
      • 2013-04-20
      • 1970-01-01
      • 1970-01-01
      • 2012-02-03
      相关资源
      最近更新 更多