如何在非async的Cmdlet重写方法中正确调用async异步方法?
问题描述
我编写了一个异步方法,需要在继承自.NET System.Management.Automation.Cmdlet 类的子类重写方法中调用它。
我平时调用该方法的方式如下:
public async Task TestReadingFile() { using MyReader reader = new MyReader(SetupMyStream("Contoso.txt")); await foreach (MyDataObject obj in reader.GetFrames()) { Debug.Print($"{reader.Progress}: {obj.GetType().Name} - {obj}"); } }
现在在我的Cmdlet子类重写的ProcessRecord方法中,无法使用async修饰,但我需要等待异步操作完成。我该怎么实现?
我原本尝试了以下代码:
protected override void ProcessRecord() { using MyReader reader = new MyReader(MyFilePath); IAsyncEnumerator<MyDataObject> enumerator = reader.GetFrames().GetAsyncEnumerator(); while (enumerator.MoveNextAsync().GetAwaiter().GetResult()) { MyDataObject obj = enumerator.Current; Debug.Print($"{reader.Progress}: {obj.GetType().Name} - {obj}"); } }
但这段代码抛出了InvalidOperationException,请问正确的实现方式是什么?
解决方案
在Cmdlet的同步方法中处理异步枚举时,核心要解决异步操作的上下文死锁问题,同时正确管理异步枚举器的生命周期。以下是两种可靠的实现方式:
方式一:手动正确处理异步枚举器生命周期
手动调用异步枚举器的MoveNextAsync和DisposeAsync,并通过ConfigureAwait(false)避免上下文绑定导致的死锁:
protected override void ProcessRecord() { using MyReader reader = new MyReader(MyFilePath); var enumerator = reader.GetFrames().GetAsyncEnumerator(); try { while (enumerator.MoveNextAsync().ConfigureAwait(false).GetAwaiter().GetResult()) { MyDataObject obj = enumerator.Current; Debug.Print($"{reader.Progress}: {obj.GetType().Name} - {obj}"); } } finally { // 确保异步枚举器被正确释放 enumerator.DisposeAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } }
方式二:封装异步逻辑到线程池上下文
利用Task.Run将异步逻辑调度到线程池,脱离原始同步上下文后再阻塞等待,写法更简洁:
protected override void ProcessRecord() { Task.Run(async () => { using MyReader reader = new MyReader(MyFilePath); await foreach (MyDataObject obj in reader.GetFrames().ConfigureAwait(false)) { Debug.Print($"{reader.Progress}: {obj.GetType().Name} - {obj}"); } }).GetAwaiter().GetResult(); }
原代码报错原因
你的代码未正确处理异步枚举器的DisposeAsync操作,同时调用MoveNextAsync().GetAwaiter().GetResult()时,异步操作尝试回到被同步阻塞占用的原始上下文,触发了死锁,最终抛出InvalidOperationException。
内容的提问来源于stack exchange,提问作者AxD
相关产品推荐
相关产品推荐

