带Asp.Net核心控制器/ ServiceProviderScope的AsyncLocal

时间:2018-06-21 11:56:02

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

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

以下内容演示了该问题-只需将其放入默认的asp.net核心示例项目中,然后将TestRepo注册为临时依赖项。

在调用GET api/values/时,我们在调用开始时在静态AsyncLocal中将当前任务的值设置为5。该值按预期方式流动,毫无问题。但是,在调用之后处置了控制器及其依赖项之后,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 :(
        }
    }
}

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

1 个答案:

答案 0 :(得分:2)

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

您基本上有两个选择:

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

某些DI容器实际上应用了第二种技术。例如,简单注入器使用基于环境状态的作用域,并在封面下使用AsyncLocal<T>。当集成到ASP.NET Core中时,它将请求包装在适用于此范围的中间件中。这意味着从Simple Injector解析的所有Scoped组件都将在ASP.NET Core管道处置其服务之前被处置,并且这会在异步上下文仍然可用的情况下发生。

相关问题