Skip to content

AsyncLocal loses state when ServiceProviderScope is Disposed #3249

@danstur

Description

@danstur

It seems like AsyncLocal does not keep its state until the scope created for executing a controller is finished disposed. This means that a value set in AsyncLocal during a controller call is not available when the injected dependencies are disposed - instead the value is always null.

Not sure if this is intended or not, but it'd be nice to document this behavior somewhere and maybe provide a work around. Using AsyncLocal to store some information that's required during a call is not unheard of and if you store resources that have to be cleaned up in it, this is rather cumbersome currently.

Example. Put the following into the default asp.net core Web API project template for netcoreapp2.1 and register TestRepo as a transient dependency. When executing GET api/values we get an exception during TestRepo::Dispose because the asynclocal value is null instead of 5.

[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()
    {
        return _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 :(");
        }
    }
}

Current behavior: Dispose throws a InvalidOperationException.
Expected behavior: GetValue() returns 5 inside TestRepo::Dispose

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions