【问题标题】:Exceptions not being caught in Async/Await Task operations异步/等待任务操作中未捕获的异常
【发布时间】:2017-03-23 17:14:15
【问题描述】:

我正在.NET Core 中编写一个 ASP.NET MVC 站点。我正在尝试封装一些常见的异常处理。在基类中我有这个方法。

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 响应。

我读到的每一个地方都说你只需要在 try 中等待任务,然后任何异常都会被捕获,但我的从来没有。


已解决

我终于能够解决这个问题。感谢曾先生的帮助。

【问题讨论】:

  • 不要在 ASP.NET Core 应用程序中调用 Task.Run。您不会从中获得任何好处,甚至可能会降低您的性能,并且您可能会弄乱 ThreadPool 管理。仅使用真正运行异步的 await/async 操作(文件 IO、网络、数据库访问等)
  • msdn.microsoft.com/en-us/magazine/dn802603.aspx 是关于 ASP.NET 但仍然适用于 ASP.NET Core
  • @Tseng 我不明白如何在不使用 Task.Run 的情况下使 operation.Invoke() 异步。没有操作。InvokeAsync()。我尝试使用 TaskFactory.FromAsync(operation.BeginInvoke, operation.EndInvoke, null),但这引发了异常。
  • 除非您调用的操作已经是异步且真正的异步操作(读取文件、执行网络请求或数据库请求等)。只需同步运行它。在 ASP.NET Core 中异步运行同步操作没有任何好处,它甚至可能会损害您的性能。这与桌面开发不同,您使用它在后台运行东西而不锁定 UI 线程
  • 否则要在回调中运行异步代码,将其转为返回任务:Func&lt;Task&lt;IActionResult&gt;,然后您可以执行类似的操作,然后执行类似的操作:() =&gt; await _someService.GetAsync();。和return await handleException.Invoke();

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


【解决方案1】:

您的代码中存在逻辑错误。你调用return await Task.Run(() =&gt; handleException.Invoke());,但在函数委托中你运行异步代码而不等待它(这里:await ExceptionHandledOperationAsync(() =&gt; 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)移动到方法中,如上所示。但这确实是丑陋的代码。恕我直言,SomeService 应该自己处理服务故障并在找不到任何值时返回null。

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

【讨论】:

  • 是的,您对 NotFound 的评论是正确的。我的实际代码比这更复杂。我实际上有一个覆盖,它接受特定异常类型的映射集合,这些异常类型将根据匹配类型返回相应的 IActionResults。我刚刚删除了所有这些复杂性,目的是展示我解决异常处理的问题。感谢你的帮助。我也让它的工作方式略有不同,但可能会改用你的模式。
【解决方案2】:

我同意上面关于不在 ASP.NET 应用程序中使用 Task.Run 的评论。话虽如此,您可以尝试在您的 Invoke 方法周围放置一个 try / catch。

例子:

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

【讨论】:

  • 我试过这个。我无法让它工作。也许我错过了关于“捕获的 SynchronizationContext”的评论中隐含的一些东西。我真的不明白该怎么办。
  • 这只是一个示例。我试图向您展示如何使用 try/catch 包装您的代码。什么不工作?
【解决方案3】:

在终于理解了 Tseng 告诉我的内容后,我得以完成这项工作。我就是这样改的:

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);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-18
    • 1970-01-01
    • 2014-09-06
    相关资源
    最近更新 更多