尝试使用用 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()
是这里的关键部分。
所以我尝试采用 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()
是这里的关键部分。