尝试使用用 C# 编写的 Cmdlet 在 PowerShell 中获取 IAsyncEnumerable 方法的结果

Trying to Get Results of an IAsyncEnumerable Method in PowerShell with a Cmdlet Written in C#

所以我尝试采用 IAsyncEnumerable 方法并在 PowerShell 中传递上述方法的结果。我知道 PowerShell 没有异步支持,一般来说,人们使用 Task.GetAwaiter().GetResult() 作为获得结果的方式。

但是,这种方法对 IAsyncEnumerable 方法不起作用(或者至少我不知道如何实现它)。

我的具体情况稍微复杂一些,但让我们举个例子:

namespace ServerMetadataCache.Client.Powershell
{
    [Cmdlet(VerbsDiagnostic.Test,"SampleCmdlet")]
    [OutputType(typeof(FavoriteStuff))]
    public class TestSampleCmdletCommand : PSCmdlet
    {
        [Parameter(
            Mandatory = true,
            Position = 0,
            ValueFromPipeline = true,
            ValueFromPipelineByPropertyName = true)]
        public int FavoriteNumber { get; set; } = default!;

        [Parameter(
            Position = 1,
            ValueFromPipelineByPropertyName = true)]
        [ValidateSet("Cat", "Dog", "Horse")]
        public string FavoritePet { get; set; } = "Dog";


        private IAsyncEnumerable<int> InternalMethodAsync()
        {
            for (int i = 1; i <= 10; i++)
            {
                await Task.Delay(1000);//Simulate waiting for data to come through. 
                yield return i;
            } 
        }

        protected override void EndProcessing()
        {   
          //this is the issue... how can I tell powershell to read/process results of 
          //InternalMethodAsync()? Regularly trying to read the method doesn't work
          //and neither does wrapping the method with a task and using GetAwaiter().GetResult()
        }

    public class FavoriteStuff
    {
        public int FavoriteNumber { get; set; } = default!;
        public string FavoritePet { get; set; } = default!;
    }

}

这个 cmdlet 当然是一个虚拟的,只接受一个整数和“狗”、“猫”或“马”,但我更关心的是如何处理 Cmdlet 中的 InternalMethodAsync()。挑战在于绕过 IAsyncEnumerable.

制作一个异步包装器方法,将并发集合(如 ConcurrentQueue<int>)作为参数并用 IAsyncEnumerable<int> 中的项目填充它,然后在任务完成之前开始读取它:

private async Task InternalMethodExchangeAsync(IProducerConsumerCollection<int> outputCollection)
{
    await foreach(var item in InternalMethodAsync())
        outputCollection.TryAdd(item)
}

然后在EndProcessing():

protected override void EndProcessing()
{
    var resultQueue = new ConcurrentQueue<int>();
    var task = InternalMethodExchangeAsync(resultQueue);

    // Task still running? Let's try reading from the queue!
    while(!task.IsCompleted)
        if(resultQueue.TryDequeue(out int preliminaryResult))
            WriteObject(preliminaryResult);

    // Process remaining items
    while(resultQueue.Count > 0)
        if(resultQueue.TryDequeue(out int trailingResult))
            WriteObject(trailingResult);
}

真的帮我弄明白了。

它返回获取 Task.GetAwaiter().GetResult() - 它适用于常规任务。您需要将枚举器的结果作为任务处理(我之前曾尝试这样做,但方式不正确)。因此,根据我问题中的 InternalMethodAsync(),可以像这样处理结果:

private void processDummy(){
       var enumerator = InternalMethodAsync().GetAsyncEnumerator();
       while (enumerator.MoveNextAsync().AsTask().GetAwaiter().GetResult()){
              Console.WriteLine(enumerator.Current);
       }
}

AsTask() 是这里的关键部分。