在目前版本中非同步迭代使用 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.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。故事還沒完,待續........