我最近遇到了这个问题,发现创建 PowerShell 作业似乎也能很好地解决问题。这为您提供了标准作业功能(Wait-Job、Receive-Job 和 Remove-Job)。
工作可能令人生畏,但这很简单。它是用 C# 编写的,因此您可能需要使用 Add-Type 添加它(需要对其编写方式进行一些调整,当我使用 lambda 时,Add-Type -TypeDefintition '...' 似乎失败了,所以它们需要替换为正确的 Get 访问器)或编译它。
using System;
using System.Management.Automation;
using System.Threading;
using System.Threading.Tasks;
namespace MyNamespace
{
public class TaskJob : Job
{
private readonly Task _task;
private readonly CancellationTokenSource? _cts;
public override bool HasMoreData => Error.Count > 0 || Output.Count > 0;
public sealed override string Location => Environment.MachineName;
public override string StatusMessage => _task.Status.ToString();
public override void StopJob()
{
// to prevent the job from hanging, we'll say the job is stopped
// if we can't stop it. Otherwise, we'll cancel _cts and let the
// .ContinueWith() invocation set the job's state.
if (_cts is null)
{
SetJobState(JobState.Stopped);
}
else
{
_cts.Cancel();
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_task.Dispose();
_cts?.Dispose();
}
base.Dispose(disposing);
}
public TaskJob(string? name, string? command, Task task, CancellationTokenSource? cancellationTokenSource)
: base(command, name)
{
PSJobTypeName = nameof(TaskJob);
if (task is null)
{
throw new ArgumentNullException(nameof(task));
}
_task = task;
task.ContinueWith(OnTaskCompleted);
_cts = cancellationTokenSource;
}
public virtual void OnTaskCompleted(Task task)
{
if (task.IsCanceled)
{
SetJobState(JobState.Stopped);
}
else if (task.Exception != null)
{
Error.Add(new ErrorRecord(
task.Exception,
"TaskException",
ErrorCategory.NotSpecified,
task)
{
ErrorDetails = new ErrorDetails($"An exception occurred in the task. {task.Exception}"),
}
);
SetJobState(JobState.Failed);
}
else
{
SetJobState(JobState.Completed);
}
}
}
public class TaskJob<T> : TaskJob
{
public TaskJob(string? name, string? command, Task<T> task, CancellationTokenSource? cancellationTokenSource)
: base(name, command, task, cancellationTokenSource)
{
}
public override void OnTaskCompleted(Task task)
{
if (task is Task<T> taskT)
{
try
{
Output.Add(PSObject.AsPSObject(taskT.GetAwaiter().GetResult()));
}
// error handling dealt with in base.OnTaskCompleted
catch { }
}
base.OnTaskCompleted(task);
}
}
}
将此类添加到您的 PowerShell 会话后,您可以非常轻松地将任务转换为任务:
$task = [MyNamespace.MyClass]::MyStaticMethod($myParam)
$job = ([MyNamespace.TaskJob]::new('MyTaskJob', $MyInvocation.Line, $task, $null))
# Add the job to the repository so that it can be retrieved later. This requires that you're using an advanced script or function (has an attribute declaration, particularly [CmldetBinding()] before the param() block). If not, you can always make a Register-Job function to just take an unregistered job and add it to the job repository.
$PSCmdlet.JobRepository.Add($job)
# now you can do all this with your task
Get-Job 'MyTaskJob' | Wait-Job
Get-Job 'MyTaskJob' | Receive-Job
Get-Job 'MyTaskJob' | Remove-Job
我会指出我对任务并不是非常熟悉,所以如果有人看到那里看起来很糟糕的东西,请告诉我,我一直在寻找改进的方法。 :)
可以在this TaskJob gist 中找到更完善的概念。