UFO ET IT

PowerShell에서 비동기 C# 메서드 대기

ufoet 2023. 8. 21. 21:54
반응형

PowerShell에서 비동기 C# 메서드 대기

다음과 같은 정적 멤버 액세스 장치를 사용하여 PowerShell에서 정적 비동기 C# 메서드를 호출하려고 합니다.

파워셸

function CallMyStaticMethod([parameter(Mandatory=$true)][string]$myParam)
{
    ...
    [MyNamespace.MyClass]::MyStaticMethod($myParam)
    ...
}

C#

public static async Task MyStaticMethod(string myParam)
{
    ...
    await ...
    ...
}

내 C# 메서드는 비동기식이므로 PowerShell에서 일종의 "대기" 호출 없이 C# 메서드가 제대로 실행될 수 있습니까?

자체적으로 잘 실행되지만 완료될 때까지 기다리려면 이 기능을 사용할 수 있습니다.

$null = [MyNamespace.MyClass]::MyStaticMethod($myParam).GetAwaiter().GetResult()

이렇게 하면 포장이 풀립니다.AggregateException만약 당신이 같은 것을 사용한다면 그것은 던져질 것입니다.$task.Result대신.

하지만 그것은 완성될 때까지 차단될 것이고, 그것은 그것을 막을 것입니다.CTRL + C파이프라인이 제대로 정지되지 않습니다.파이프라인 중지를 준수하는 동안 이 작업이 완료될 때까지 기다릴 수 있습니다.

 $task = [MyNamespace.MyClass]::MyStaticMethod($myParam)
 while (-not $task.AsyncWaitHandle.WaitOne(200)) { }
 $null = $task.GetAwaiter().GetResult()

비동기 메서드가 실제로 무언가를 반환하는 경우 제거$null =

패트릭 마인케의 대답을 빌려, 당신을 위해 작업(또는 작업 목록)을 해결해 줄 파이프라인이 가능한 기능을 만들 수 있습니다.

function Await-Task {
    param (
        [Parameter(ValueFromPipeline=$true, Mandatory=$true)]
        $task
    )

    process {
        while (-not $task.AsyncWaitHandle.WaitOne(200)) { }
        $task.GetAwaiter().GetResult()
    }
}

용도:

$results = Get-SomeTasks $paramA $paramB | Await-Task

최근에 우연히 이 문제를 발견했는데 PowerShell 작업을 생성하는 것도 꽤 효과적인 방법인 것 같습니다.이렇게 하면 표준 작업 기능(대기-작업, 수신-작업 및 제거)이 제공됩니다.일은 힘들 수 있지만, 이것은 꽤 간단합니다.C#으로 작성되었으므로 Add-Type을 사용하여 추가하거나 컴파일해야 할 수 있습니다(작성 방법을 수정해야 합니다. Add-TypeDefinition '...'은 lamdas를 사용할 때 실패하는 것 같습니다. 따라서 적절한 Get accessor로 대체해야 합니다.).

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

저는 제가 업무에 대해 매우 익숙하지 않다는 것을 지적할 것입니다. 그래서 누군가 위에서 나쁘게 보이는 것을 본다면, 저는 항상 개선할 방법을 찾고 있습니다.:)

보다 발전된 개념은 이 작업 작업 안내서에서 확인할 수 있습니다.

언급URL : https://stackoverflow.com/questions/51218257/await-async-c-sharp-method-from-powershell

반응형