Skip to content

AsyncLocal<T> does not survive across yield return statement #47802

Description

@CXuesong

Description

I'm not sure this is bug or by-design. Consider the following use case of AsyncLocal<T>. Should I expect stringLocal.Value to be "Test" or null?

var stringLocal = new AsyncLocal<string>();

async IAsyncEnumerable<int> IntSequence()
{
    stringLocal.Value = "Test";
    yield return 10;
    // Now what should happen if we do not explicitly capture the execution context???
    Console.WriteLine("stringLocal.Value: {0}", stringLocal.Value);
}

From what I can observe currently, stringLocal.Value after yield return is null.

Configuration

.NET 5 / .NET 6.0.100-preview.1.21103.9

Regression?

N / A

Other information

Here is an extensive test around the persistence of AsyncLocal<T>. [.NET Fiddle]

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

var stringLocal = new AsyncLocal<string>();

async IAsyncEnumerable<int> IntSequence(string localValue)
{
    stringLocal.Value = localValue;
    Console.WriteLine("Set up stringLocal: {0}.", localValue);
    await Task.Delay(100).ConfigureAwait(false);
    // await didn't affect AsyncLocal flow
    Console.WriteLine("stringLocal.Value: {0}", stringLocal.Value);

    using var execContext = ExecutionContext.Capture();
    Debug.Assert(execContext != null);      // i.e. ExecutionContext.IsFlowSuppressed() == false
    yield return 10;
    ExecutionContext.Restore(execContext);
    // This gives us the correct value, as we have restored execution context.
    Console.WriteLine("stringLocal.Value: {0}", stringLocal.Value);

    // Now what should happen if we do not explicitly capture the execution context???
    yield return 20;
    Console.WriteLine("stringLocal.Value: {0}", stringLocal.Value);
    yield return 30;
    Console.WriteLine("stringLocal.Value: {0}", stringLocal.Value);
}

async Task PrintSequence1()
{
    await foreach (var i in IntSequence("value 1"))
    {
        Console.WriteLine("Sequence: {0}", i);
        await Task.Delay(100);
    }
}

async Task PrintSequence2()
{
    await using var it = IntSequence("value 2").GetAsyncEnumerator();
    while (await it.MoveNextAsync())
    {
        Console.WriteLine("Sequence: {0}", it.Current);
    }
}

PrintSequence1().Wait();
Console.WriteLine();
PrintSequence2().Wait();

Here is the output

Set up stringLocal: value 1.
stringLocal.Value: value 1
Sequence: 10
stringLocal.Value: value 1
Sequence: 20
stringLocal.Value: 
Sequence: 30
stringLocal.Value: 

Set up stringLocal: value 2.
stringLocal.Value: value 2
Sequence: 10
stringLocal.Value: value 2
Sequence: 20
stringLocal.Value: 
Sequence: 30
stringLocal.Value: 

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions