C# 8.0 搶先看-- Async Stream
阿新 • • 發佈:2018-12-22
非同步流?
Async Stream 簡單說來是一種非同步的迭代器模式,說更白一點就是可以await 的foreach。在過去的C# 中如果要回傳一個可迭代的IEnumerable<T> ,我們可能會這樣寫:
public class EnumerableProcess { async static public Task<IEnumerable<string>> ReadLineAsync(string path) { List<string> list = new List<string>(); using (StreamReader reader = File.OpenText(path)) { while (await reader.ReadLineAsync() is string result) { list.Add(result); await Task.Delay(100); } } return list; } }
這是一個非同步一行一行讀取文字檔的例子,這個例子裡的回傳型別是一個Task<IEnumerable<string>> ,外部程式碼將會這樣呼叫這個方法:
var r = await EnumerableProcess.ReadLineAsync(path); foreach (var item in r) { Console.WriteLine(item); }
這造成一個長時等待的問題,因為呼叫端必須等待ReadLineAsync 這個Task 整個完成後才能回傳;所以C# 8.0 引入了Async Stream 使得非同步的迭代得以實現, 這件事情不僅僅牽涉到編譯器,也需要一些新的型別,主要是以下三個:
(1) IAsyncDisposable -- IAsyncEnumerator<out T> 將會拓展這個介面
public interface IAsyncDisposable { ValueTask DisposeAsync(); }
(2)IAsyncEnumerator <out T>
public interface IAsyncEnumerator<out T> : IAsyncDisposable { T Current { get; } ValueTask<bool> MoveNextAsync(); }
(3)IAsyncEnumerable <out T>
public interface IAsyncEnumerable<out T> { IAsyncEnumerator<T> GetAsyncEnumerator(); }
實作Async Stream
由於此時在框架中對於整個Async Stream 的實作尚未完整,所以沒辦法直接使用yield return,先示範最基本的寫法,建立一個類別,並且實作以上介面:
sealed class AsyncFileProcess : IAsyncEnumerable<string>, IAsyncEnumerator<string> { private readonly StreamReader _reader; private bool _disposed; public AsyncFileProcess(string path) { _reader = File.OpenText(path); _disposed = false; } public string Current { get; private set; } public IAsyncEnumerator<string> GetAsyncEnumerator() { return this; } async public ValueTask<bool> MoveNextAsync() { await Task.Delay(100); 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; } } }
呼叫端就可以這樣呼叫它:
var process = new AsyncFileProcess("SourceFile.txt"); try { await foreach (var s in process) { Console.WriteLine(s); } Console.ReadLine(); } finally { await process.DisposeAsync(); }
你可以感受到第一個例子是停頓了很久之後,蹦一下全跳出來;而第二的例子則會一行行跑出來(為了強化這個效果在兩方都加了Task.Delay )。在第二個例子的呼叫端可以看到await foreach 的使用。