Skip to content

Code after finally in IAsyncEnumerable is not executed in some cases. #43936

@gurustron

Description

@gurustron

From this stackoverflow question.

Repro:

TargetFramework: netcoreapp3.1
.NET Core SDK (reflecting any global.json):
Version: 3.1.201
Commit: b1768b4ae7

Runtime Environment:
OS Name: Windows
OS Version: 10.0.18362

    class Program
    {
        static async Task Main()
        {
            await foreach (var item in new MyAsyncIterator<int>(GetStream()))
            {
                Console.WriteLine($"Received: {item}");
            }
            Console.WriteLine($"Done");
        }

        static async IAsyncEnumerable<int> GetStream()
        {
            await Task.Delay(1);
            try
            {
                yield return 1;
                yield return 2;
            }
            finally
            {
                try {}
                finally{}
                Console.WriteLine("This will not be printed if '_taken++' is not commented");
            }
            Console.WriteLine("This will not be printed if '_taken++' is not commented");
        }
    }

    public class MyAsyncIterator<T> : IAsyncEnumerable<T>, IAsyncEnumerator<T>
    {
        private readonly IAsyncEnumerable<T> _source;
        private IAsyncEnumerator<T>? _enumerator;
         T _current = default!;
        public T Current => _current;

        public MyAsyncIterator(IAsyncEnumerable<T> source)
        {
            _source = source;
        }

        public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()) => this;

        public async ValueTask DisposeAsync()
        {
            if (_enumerator != null)
            {
                await _enumerator.DisposeAsync().ConfigureAwait(false);
                _enumerator = null;
            }
        }

        private int _taken;
        public async ValueTask<bool> MoveNextAsync()
        {
            _enumerator ??= _source.GetAsyncEnumerator();

            if (_taken < 1 && await _enumerator!.MoveNextAsync().ConfigureAwait(false))
            {
                 _taken++; // COMMENTING IT OUT MAKES IT WORK
                _current = _enumerator.Current;
                return true;
            }

            return false;
        }
    }

IL like this is generated after the finally blocks:

          IL_0120: ldarg.0      // this
          IL_0121: ldfld        bool TestAsyncEnum.Program/'<GetStream>d__1'::'<>w__disposeMode'
          IL_0126: brfalse.s    IL_012a
          IL_0128: br.s         IL_0136

          // [31 17 - 31 63]
          IL_012a: ldstr        "This will not be printed"
          IL_012f: call         void [System.Console]System.Console::WriteLine(string)
          IL_0134: nop

          // [32 13 - 32 14]
          IL_0135: nop

          IL_0136: endfinally
        } // end of finally

So if the enumerator has not enumerated fully w__disposeMode is set to true and code after finally is not executed.

Metadata

Metadata

Assignees

Type

No type

Projects

Status

Active/Investigating

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions