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

Time:10-07

So I am trying to take an IAsyncEnumerable method and relay the results of said method in PowerShell. I understand that PowerShell does not have async support, and generally, people use the Task.GetAwaiter().GetResult() as the means to get their result.

However, this approach does not work (or at least I don't know how to implement it) for IAsyncEnumerable methods.

My specific case is a little more sophisticated, but let's take this example:

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!;
    }

}

This cmdlet is of course a dummy that just takes in a integer and either "Dog", "Cat" or "Horse", but my bigger concern is how to process the InternalMethodAsync() in the Cmdlet. The challenge is getting around the IAsyncEnumerable.

CodePudding user response:

Make an async wrapper method that takes a concurrent collection - like a ConcurrentQueue<int> - as a parameter and fills it with the items from the IAsyncEnumerable<int>, then start reading from it before the task completes:

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

Then in 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);
}

CodePudding user response:

This question really helped me figure it out.

It Goes back to get Task.GetAwaiter().GetResult() - which works on regular tasks. You need to process the enumerator's results as tasks (which I was trying to do earlier but not in the right way). So based on the InternalMethodAsync() in my question, one could process the results like so:

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

The AsTask() was the key piece here.

  • Related