C# 8.0 搶先看 -- Async Stream (2)

在目前版本中非同步迭代使用 yield return 的暫時解決方案說明。

本篇文章使用環境
開發環境 Visual Studio 2019 Preview 1 (16.0.0 Preview 1)
框架       .NET Core 3.0.0-preview-27122-01
編譯器    C# 8.0 beta

上一篇簡單示範了在類別中實作 Async Stream 的方式, 如果今天是一個方法要回傳 IAsyncEnumerable<T> ,而方法內部使用 yield return 該怎麼寫呢?

我們一樣就拿 ReadLineAsync 來示範,首先建立一個類別實作 IAsyncEnumerator<T> ,當然這也包含了實作 IAsyncDisposable:

    internal class AsyncEnumerator : IAsyncEnumerator<string>
    {
        private readonly StreamReader _reader;

        private bool _disposed;

        public string Current { get; private set; }

        public AsyncEnumerator(string path)
        {
            _reader = File.OpenText(path);
            _disposed = false;
        }
        async public ValueTask<bool> MoveNextAsync()
        {
            var result = await _reader.ReadLineAsync();
            Current = result;
            return result != null;
        }
        async public ValueTask DisposeAsync()
        {
            await Task.Run(() => Dispose());
        }

        private void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!this._disposed)
            {
                if (_reader != null)
                {
                    _reader.Dispose();
                }
                _disposed = true;
            }
        }
    }

接著建立另外一個類別, 這個類別很簡單,只包含一個靜態的方法 async static public IAsyncEnumerable<string> ReadLineAsync(string path),實作內容如下:

        async static public IAsyncEnumerable<string> ReadLineAsync(string path)
        {

            var enumerator = new AsyncEnumerator(path);
            try
            {
                while (await enumerator.MoveNextAsync())
                {
                    await Task.Delay(100);
                    yield return enumerator.Current;
                }
            }
            finally
            {
                await enumerator.DisposeAsync();
            }
        }
    }

程式碼沒有錯,但編譯過不了,觀察一下錯誤訊息:

error CS0656: Missing compiler required member 'System.Threading.Tasks.ManualResetValueTaskSourceLogic`1.GetResult'
error CS0656: Missing compiler required member 'System.Threading.Tasks.ManualResetValueTaskSourceLogic`1.GetStatus'
error CS0656: Missing compiler required member 'System.Threading.Tasks.ManualResetValueTaskSourceLogic`1.get_Version'
error CS0656: Missing compiler required member 'System.Threading.Tasks.ManualResetValueTaskSourceLogic`1.OnCompleted'
error CS0656: Missing compiler required member 'System.Threading.Tasks.ManualResetValueTaskSourceLogic`1.Reset'
error CS0656: Missing compiler required member 'System.Threading.Tasks.ManualResetValueTaskSourceLogic`1.SetException'
error CS0656: Missing compiler required member 'System.Threading.Tasks.ManualResetValueTaskSourceLogic`1.SetResult'
error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.IStrongBox`1.get_Value'
error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.IStrongBox`1.Value'

很明顯,編譯器需要兩個型別 (1)  System.Threading.Tasks.ManualResetValueTaskSourceLogic<T> (2) System.Runtime.CompilerServices.IStrongBox<T> 才能完成編譯。感謝 open source 與 git hub,在微軟的 dotnet/corclr 的專案中找到了這麼一段討論 ~~ ManualResetValueTaskSourceLogic`1 missing in System.Private.CoreLib #21379 ,有位 stephentoub (應該是微軟員工而且是這個專案的成員) 提到『It's not missing exactly, but like @benaadams said things are just out-of-sync between the compiler and library in Preview 1. The compiler is looking for the old design (ManualResetValueTaskSourceLogic<T> and IStrongBox<T>), while the libraries include the approved API surface area (ManualResetValueTaskSourceCore<T>), and we didn't have time to get the compiler updated. You just need to include a bit of boilerplate in your app』,簡單說就是編譯器和框架目前的更新進度不一致,導致少了點甚麼。既然如此,我們就遵照本草綱目的指示,補上這兩個型別,請注意,這兩個型別的命名空間必須正確:

using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks.Sources;


namespace System.Threading.Tasks{
   
    internal struct ManualResetValueTaskSourceLogic<TResult>
    {
        private ManualResetValueTaskSourceCore<TResult> _core;
        public ManualResetValueTaskSourceLogic(IStrongBox<ManualResetValueTaskSourceLogic<TResult>> parent) : this() { }
        public short Version => _core.Version;
        public TResult GetResult(short token) => _core.GetResult(token);
        public ValueTaskSourceStatus GetStatus(short token) => _core.GetStatus(token);
        public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags);
        public void Reset() => _core.Reset();
        public void SetResult(TResult result) => _core.SetResult(result);
        public void SetException(Exception error) => _core.SetException(error);
    }
}

namespace System.Runtime.CompilerServices
{
    internal interface IStrongBox<T> { ref T Value { get; } }
}

補上去後就大功告成,可以快樂地非同步 yielld return。故事還沒完,待續........