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.