【问题标题】:AsyncLocal with ASP.NET Core Controller/ServiceProviderScope带有 ASP.NET Core 控制器/ServiceProviderScope 的 AsyncLocal
【发布时间】:2018-11-30 18:07:08
【问题描述】:

在控制器范围内解析的元素上调用Dispose 之前,似乎不会保留执行上下文。这可能是因为 asp.net 核心必须在本机代码和托管代码之间跳转,并在每次跳转时重置执行上下文。似乎在释放范围之前不再恢复正确的上下文。

下面演示了这个问题 - 只需将它放在默认的 asp.net 核心示例项目中,并将 TestRepo 注册为临时依赖项。

当调用GET api/values/ 时,我们在调用开始时在静态 AsyncLocal 中将当前任务的值设置为 5。该值按预期通过 await 流动,没有任何问题。但是,当控制器及其依赖项在调用后被释放时,AsyncLocal 上下文已经被重置。

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly TestRepo _testRepo;

    public ValuesController(TestRepo testRepo) => _testRepo = testRepo;

    [HttpGet()]
    public async Task<IActionResult> Get()
    {
        _testRepo.SetValue(5);
        await Task.Delay(100);
        var val = _testRepo.GetValue(); // val here has correctly 5.
        return Ok();
    }
}

public class TestRepo : IDisposable
{
    private static readonly AsyncLocal<int?> _asyncLocal = new AsyncLocal<int?>();

    public int? GetValue() => _asyncLocal.Value;

    public void SetValue(int x) => _asyncLocal.Value = x;

    public void Foo() => SetValue(5);

    public void Dispose()
    {
        if (GetValue() == null)
        {
            throw new InvalidOperationException(); //GetValue() should be 5 here :(
        }
    }
}

这是故意的吗?如果是,是否有解决此问题的方法?

【问题讨论】:

  • 在瞬态范围的注入上绑定一个静态使我的大脑受伤。你到底想做什么?对于 Web 应用程序,静态几乎总是错误的方法,尤其是现在在 ASP.NET Core 的世界中,everything 现在都是依赖注入的。
  • 我不同意@ChrisPratt 对此的看法。使用静态AsyncLocal&lt;T&gt; 没有任何问题,只要将这个值封装在内部即Composition Root中。
  • @Steven 是的 AsyncLocal 只是一个非常特殊的类,其中天真的“哦,永远不要使用静态!”根本没有意义。它的全部意义在于存储全局状态 - 所以确保您可以避免将其设为静态,而是将类设为单例,但这只不过是装点门面而已。地狱的 asp.net 核心人员同意,因为他们以完全相同的方式使用该类。反对票通过复杂的问题证明了问题——人们没有花时间去理解问题,或者根本没有经验来理解问题所在。..
  • 您看到的行为是 ASP.NET Core 工作方式中的一个不幸的怪癖(Web API 具有相同的行为)。处理是在请求结束时完成的,由于某种原因,异步上下文在该点之前已经被清除。
  • @Steven Yes 很害怕。我没有想到您想到的解决方法吗?

标签: c# .net asp.net-core dependency-injection .net-core


【解决方案1】:

您看到的行为是 ASP.NET Core 工作方式的一个不幸的怪癖。我不清楚微软为什么选择这种行为,但它似乎是从 Web API 的工作方式中复制而来的,它具有确切的行为。处理显然是在请求结束时完成的,但由于某种原因,异步上下文在此之前已经被清除,因此无法在单个异步上下文中运行完整的请求。

你基本上有两个选择:

  1. 不是使用环境状态来共享状态,而是通过对象图流状态而不是使用环境状态。换句话说,将TestRepo 设为Scoped,并将value 存储在私有字段中。
  2. 将使用该值的操作移到请求的较早阶段。例如,您可以定义一些中间件来包装请求并在最后调用该操作。在那个阶段,异步上下文仍然存在。

一些 DI 容器实际上应用了第二种技术。例如,Simple Injector 使用基于环境状态的作用域,在幕后使用AsyncLocal&lt;T&gt;。当集成到 ASP.NET Core 中时,它将请求包装在一个应用此范围的中间件中。这意味着从 Simple Injector 解析的任何 Scoped 组件都将在 ASP.NET Core 管道释放其服务之前被释放,并且在异步上下文仍然可用时发生。

【讨论】:

  • 嗨斯蒂芬,真的很有帮助的答案。我有一个简短的后续问题。默认的 Asp Net Core DI 在幕后使用什么来应用和检索作用域上下文?
猜你喜欢
  • 1970-01-01
  • 2016-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多