异常未在异步/等待任务操作中捕获

时间:2016-11-09 19:37:59

标签: asynchronous exception-handling asp.net-core async-await task

我在.NET Core中编写ASP.NET MVC站点。我试图封装一些常见的异常处理。在Base类中,我有这种方法。

public abstract class BaseController<TController> : Controller where TController : Controller
{
    protected IActionResult ExceptionHandledOperation(Func<IActionResult> operation, Func<IActionResult> handleException)
    {
        try
        {
            return operation.Invoke();
        }
        catch (Exception exception)
        {
            Logger.LogError($"Operation {Request.Path} Exception", exception);

            return handleException.Invoke();
        }
    }
}

从继承自这个基类的控制器,我像这样使用这个方法:

[Route("api/[controller]")]
public class MyController : BaseController<MyController>
{
    [HttpGet]
    public IActionResult Get()
    {
        return ExceptionHandledOperation(() => Ok(_someService.GetAsync().Result),
                                         NotFound);
    }
}

假设_someService.GetAsync()方法是这样的:

public class SomeService
{
    public async Task<PreconfigurationData> GetAsync()
    {
        // http request removed for brevity

        if (!response.IsSuccessStatusCode)
            throw new Exception("SomeService Exception");
    }
}

这很好用,会在基类方法中捕获我的异常并返回NotFound结果。

但是,我想避免从SomeService.GetAsync方法调用.Result。我读到它的任何地方都说不要这样做,因为它可能会陷入僵局。

所以我将我的基本控制器修改为:

public abstract class BaseController<TController> : Controller where TController : Controller
{
    protected async Task<IActionResult> ExceptionHandledOperationAsync(Func<IActionResult> operation, Func<IActionResult> handleException)
    {
        try
        {
            return await Task.Run(() => operation.Invoke());
        }
        catch (Exception exception)
        {
            Logger.LogError($"Operation {Request.Path} Exception", exception);

            return await Task.Run(() => handleException.Invoke());
        }
    }
}

MyController是这样的:

[Route("api/[controller]")]
public class MyController : BaseController<MyController>
{
    [HttpGet]
    public async Task<IActionResult> Get()
    {
        return await ExceptionHandledOperationAsync(() => Ok(_someService.GetAsync()),
                                                    NotFound);
    }
}

但是,从我的SomeService.GetAsync方法抛出的异常永远不会被捕获,并且在处理异常时我从未得到我打算发送的NotFound响应。

我读到的任何地方都说你只需要等待试验中的任务,然后任何异常都会被捕获,但我的确没有。

解决

我终于能够解决这个问题了。感谢Tseng的帮助。

3 个答案:

答案 0 :(得分:1)

您的代码中存在逻辑错误。您调用return await Task.Run(() => handleException.Invoke());但在函数委托内部运行异步代码而不等待它(此处:await ExceptionHandledOperationAsync(() => Ok(_someService.GetAsync()), NotFound)

所以在你的try / catch块中,该方法被执行并立即返回,之前异步调用完成。

就像我的赞扬所指出的那样,你的职能委托也需要等待,阅读:返回Task

public abstract class BaseController<TController> : Controller where TController : Controller
{
    protected async Task<IActionResult> ExceptionHandledOperationAsync<T>(
        Func<Task<T>> operation,
        Func<object, IActionResult> successHandler,
        Func<IActionResult> exceptionHandler
    )
    {
        try
        {
            return successHandler.Invoke(await operation.Invoke());
        }
        catch (Exception exception)
        {
            //Logger.LogError($"Operation {Request.Path} Exception", exception);

            return exceptionHandler.Invoke();
        }
    }
}

[Route("api/[controller]")]
public class MyController : BaseController<MyController>
{
    [HttpGet]
    public async Task<IActionResult> Get()
    {
        return await ExceptionHandledOperationAsync(() => _someService.GetAsync(), Ok, NotFound);
    }
}

您需要将successHandler(将结果传递给Ok的位置)移动到方法中,如上所示。但这真是丑陋的代码。 Imho SomeService应该自己处理服务失败,并在找不到任何值时返回null

在异常时返回NotFound()似乎很奇怪,因为它表明该记录不存在但由于网络连接或数据序列化而可能失败。

答案 1 :(得分:0)

我同意上面关于在ASP.NET应用程序中不使用Task.Run的评论。话虽这么说,你可以尝试在你的Invoke方法中试一试。

示例:

 try
 {
     return await Task.Run(() =>
     {
         try
         {
              operation.Invoke();
         }
         catch (Exception ex)
         {
              // log exception
         }
     });
  }
  catch (Exception ex)
  {
      // captured SynchronizationContext
  }

答案 2 :(得分:0)

在终于理解曾告诉我的内容之后,我能够完成这项工作。这就是我改变它的方式:

public abstract class BaseController<TController> : Controller where TController : Controller
{
    protected async Task<IActionResult> ExceptionHandledOperationAsync(Func<Task<IActionResult>> operation, Func<IActionResult> handleException)
    {
        try
        {
            return await operation.Invoke();
        }
        catch (Exception exception)
        {
            Logger.LogError($"Operation {Request.Path} Exception", exception);

            return handleException.Invoke();
        }
    }
}

MyController是这样的:

[Route("api/[controller]")]
public class MyController : BaseController<MyController>
{
    [HttpGet]
    public async Task<IActionResult> Get()
    {
        return await ExceptionHandledOperationAsync(async () => Ok(await _someService.GetAsync()),
                                                    NotFound);
    }
}