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

时间:2022-06-03 22:41:28

I'm writing a ASP.NET MVC site in .NET Core. I'm trying to encapsulate some common exception handling. In a Base class I have this method.

我正在.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();
        }
    }
}

From a controller that inherits from this base class, I utilize this method like so:

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

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

Assume the _someService.GetAsync() method is this:

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

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

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

This worked fine and would catch my exception in the base class method and return the NotFound result.

这工作正常,将在基类方法中捕获我的异常并返回NotFound结果。

However, I wanted to avoid calling .Result from the SomeService.GetAsync method. Everywhere I read it says not to do that as it can deadlock.

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

So I modified my base controller to this:

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

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

And MyController like this:

和MyController这样:

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

However, my exception thrown from my SomeService.GetAsync method is never caught and I never get the NotFound response I intend to send when handling the exception.

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

Everywhere I read it says you just need to await the task in the try and then any exceptions will be caught, but mine never does.

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


Solved

解决了

I was finally able to get this resolved. Thanks to Tseng for the help.

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

3 个解决方案

#1


1  

There is a logic error in your code. You call return await Task.Run(() => handleException.Invoke()); but inside the function delegate you run async code without awaiting it (here: await ExceptionHandledOperationAsync(() => Ok(_someService.GetAsync()), NotFound).

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

So inside your try/catch block, the method is executed and immediately returns, before the async call is finished.

因此,在try / catch块中,该方法将在异步调用完成之前执行并立即返回。

Like pointed in my commends, your function delegate needs to be awaitable too, read: returns 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);
    }
}

You'll need to move the successHandler (where you pass the result to Ok) into the method too like shown above. But it's really ugly code. Imho the SomeService should handle service failures itself and return null when it doesn't find any values.

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

Returning NotFound() on exception seems very odd, as it suggest that the record doesn't exist but it may have failed due to i.e. network connection or serialization of the data.

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

#2


0  

I do agree with the comment above about not using Task.Run in an ASP.NET application. That being said, you can try putting a try / catch around your Invoke method.

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

Example:

例:

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

#3


0  

I was able to get this working after finally understanding what Tseng was telling me. This is how I changed it:

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

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

And MyController like this:

和MyController这样:

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

#1


1  

There is a logic error in your code. You call return await Task.Run(() => handleException.Invoke()); but inside the function delegate you run async code without awaiting it (here: await ExceptionHandledOperationAsync(() => Ok(_someService.GetAsync()), NotFound).

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

So inside your try/catch block, the method is executed and immediately returns, before the async call is finished.

因此,在try / catch块中,该方法将在异步调用完成之前执行并立即返回。

Like pointed in my commends, your function delegate needs to be awaitable too, read: returns 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);
    }
}

You'll need to move the successHandler (where you pass the result to Ok) into the method too like shown above. But it's really ugly code. Imho the SomeService should handle service failures itself and return null when it doesn't find any values.

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

Returning NotFound() on exception seems very odd, as it suggest that the record doesn't exist but it may have failed due to i.e. network connection or serialization of the data.

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

#2


0  

I do agree with the comment above about not using Task.Run in an ASP.NET application. That being said, you can try putting a try / catch around your Invoke method.

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

Example:

例:

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

#3


0  

I was able to get this working after finally understanding what Tseng was telling me. This is how I changed it:

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

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

And MyController like this:

和MyController这样:

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