首页 > 解决方案 > 如何在 Powershell 中使用 System.Threading.Thread?

问题描述

我需要将 C# 应用程序移植到 Powershell。该应用程序使用线程来完成繁重的工作,我需要使应用程序的端口尽可能接近原始 C# 应用程序。问题是我无法使用System.Threading.Thread生成新线程,因为System.Threading.ThreadStart在 Powershell中要求2 个参数,而 C# 代码仅在代码中要求1,因为它是委托。

这是 C# 中生成线程的最小类示例

using System;
using System.Threading;

namespace ThreadingTest
{
    class ThreadingTest
    {
        public delegate void UpdateElementCallback(string message);

        public ThreadingTest() {
            Console.WriteLine("Hello from Thread " + Thread.CurrentThread.ManagedThreadId);
            Thread newThread = new Thread(new ThreadStart(MethodToBeCalledFromAnotherThread));
            newThread.Start();
        }

        private void UpdateElement(string message) {
            Console.WriteLine(message + Thread.CurrentThread.ManagedThreadId);
        }

        private void MethodToBeCalledFromAnotherThread() {
            UpdateElement("Hello from Thread ");
        }
    }
}

示例运行中的哪些输出

Hello from Thread 1
Hello from Thread 4

这是我尝试过的 Powershell 代码的非工作端口

Add-Type -AssemblyName System
Add-Type -AssemblyName System.Threading

class ThreadingTest {

    [Action[String]]$UpdateElementCallback = 
    {
        param([String]$message) 
        $callBackThread = [System.Threading.Thread]::CurrentThread.ManagedThreadId
        "$message $callBackThread" | Out-Host
    }

    # Constructor
    ThreadingTest() {
        # Get current thread id
        $currentThread = [System.Threading.Thread]::CurrentThread.ManagedThreadId
        # Print current thread id
        "Hello from Thread $currentThread" | Out-Host

        # Create a new thread (This line fails)
        $newThread = [System.Threading.Thread]::new([System.Threading.ThreadStart]::new($this.UpdateElementCallback))

        $newThread.Start
    }
}

$ThreadingTestObject = [ThreadingTest]::new()

示例运行中的哪些输出

Hello from Thread 21
MethodException: .../ThreadingTest.ps1:21:9
Line |
  21 |          $newThread = [System.Threading.Thread]::new([System.Threading …
     |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Cannot find an overload for "new" and the argument count: "1".

用于创建新ThreadingStart对象的 Powershell 调用具有以下签名

System.Threading.ThreadStart new(System.Object object, System.IntPtr method)

我必须传递什么作为参数才能使其工作?System.Object必须是什么,System.IntPtr必须是什么作为参数传递才能使其工作?

我想说明我需要使代码尽可能接近原始代码。我尝试了 Start-Job 和 Runspaces。运行空间工作,但它需要大量的样板。我想知道是否有办法通过使用System.Threading.Thread使其工作

标签: multithreadingpowershell

解决方案


找到的解决方法是使用 C# 在此处创建System.Threading.Thread对象并将其传递给 Powershell。然后像往常一样使用 Powershell 中的线程对象。

从 Powershell 调用 C# 并使用 System.Threading.Thread

注意:此代码仅适用于Powershell 7。以前的版本会抛出一个错误,即它们无法将类中的方法转换为 Action。

$code = @"
using System;
using System.Threading;

namespace ThreadingTest
{
    public class ThreadCreator
    {
        public Thread CreateAThread(ThreadStart threadStart) {
            return new Thread(threadStart);
        }

        public ThreadStart CreateAThreadStart(Action threadStart) {
            return new ThreadStart(threadStart);
        }
    }
}
"@

Add-Type -TypeDefinition $code -Language CSharp 

class ThreadingTestClass {

    $ThreadCreator
    [System.Threading.Thread] $NewThread
    [bool] $finishCountingThread = $false
    [int] $ClassThreadId

    # Constructor
    ThreadingTestClass() {
        # Get the current thread id
        $this.ClassThreadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId
        # Print current thread id
        Write-Host "Hello from " -NoNewline
        Write-Host "Thread $($this.ClassThreadId)" -ForegroundColor Green
        # Create a ThreadCreator object
        $this.ThreadCreator = Invoke-Expression "[ThreadingTest.ThreadCreator]::new()"
        # Get a [System.Threading.Thread] object from the C# code to run '[Void] MethodToBeCalledFromAnotherThread()' in another thread
        $this.NewThread = $this.ThreadCreator.CreateAThread($this.ThreadCreator.CreateAThreadStart($this.MethodToBeCalledFromAnotherThread))
        # Start the thread
        $this.NewThread.Start()
        # Wait for user to press Escape. This 'do until' is executed while the other thread is running in the background. It doesn't wait for $this.NewThread to finish
        do {
            $keyPressed = [System.Console]::ReadKey().Key 
        } until ($keyPressed -eq [System.ConsoleKey]::Escape)
        # Set the $finishCountingThread variable to stop counting
        $this.finishCountingThread = $true;
        # Wait for the thread to finish gracefully before ending
        $this.NewThread.Join()
    }

    [Void] MethodToBeCalledFromAnotherThread() {
        # Get the current thread id
        $currentThreadID = [System.Threading.Thread]::CurrentThread.ManagedThreadId
        # Say hello from the thread this method is running on
        Write-Host "Hello from " -NoNewline
        Write-Host "Thread $currentThreadId" -ForegroundColor Blue
        # Count from 0 to 10000
        for ($i = 0; $i -le 10000; $i++) {
            # Check boolean to know whether to finish counting or not
            if($this.finishCountingThread) {
                Write-Host "`nFinishing spawned " -NoNewline
                Write-Host "Thread $currentThreadID..." -ForegroundColor Blue
                break 
            }
            # Print on the console the current value of $i
            Write-Host "`rPress ESC to stop spawned " -NoNewline
            Write-Host "Thread $currentThreadID " -NoNewline -ForegroundColor Blue
            Write-Host "and quit program: " -NoNewline 
            Write-Host "[Counting $i in Thread $currentThreadID] " –NoNewline -ForegroundColor Blue
            Write-Host "Keys are being listened to on " -NoNewline
            Write-Host "Thread $($this.ClassThreadId)" -NoNewline -ForegroundColor Green
        }

        # Check if the counting finished before the user pressed Escape
        if (-Not $this.finishCountingThread) {
            "`nCounting finished" | Out-Host
            "Press ESC to quit program..." | Out-Host
        }
    }
}
# Set cursor visible to false to see colors without a jittering cursor
[System.Console]::CursorVisible = $false
"`n`n" | Out-Host
$ThreadingTestClassObject = [ThreadingTestClass]::new()
"`n`n" | Out-Host

该代码调用 C# 代码来创建一个对象,该对象具有创建线程的方法。调用其中两个方法来创建System.Threading.Thread 对象并返回它。然后可以从 Powershell 使用线程对象。因为传递给衍生线程 ( MethodToBeCalledFromAnotherThread ) 的方法在 Powershell 类中,所以即使它在另一个线程中,它也可以访问该类的属性。

运行到结束 在此处输入图像描述 时输出:按ESC停止计数 时输出在此处输入图像描述

替代方案 - 使用 System.ComponentModel.BackgroundWorker

以下内容没有回答如何在 Powershell 中使用System.Threading.Thread的问题,但它是不调用任何 C# 代码且不使用运行空间或作业的多线程的另一种解决方案。它使用 BackgroundWorker,如下所示。当使用表单或 WPF GUI 时,它很有用。输出与其他代码中的相同。

注意:此代码仅适用于Powershell 7。以前的版本会抛出一个错误,即它们无法将类中的方法转换为 Action。

class ThreadingTestClass {

    [System.ComponentModel.BackgroundWorker] $BackgroundWorker
    [bool] $finishCountingThread = $false
    [int] $ClassThreadId

    # Constructor
    ThreadingTestClass() {
        # Get the current thread id
        $this.ClassThreadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId
        # Print current thread id
        Write-Host "Hello from " -NoNewline
        Write-Host "Thread $($this.ClassThreadId)" -ForegroundColor Green

        # Create a BackgroundWorker object, set the event and set it to support cancellation
        $this.BackgroundWorker = [System.ComponentModel.BackgroundWorker]::new()
        $this.BackgroundWorker.add_DoWork($this.MethodToBeCalledFromAnotherThread)
        $this.BackgroundWorker.WorkerSupportsCancellation = $true
        # Run the method 'MethodToBeCalledFromAnotherThread' in another thread
        $this.BackgroundWorker.RunWorkerAsync()

        # Wait for user to press Escape. This 'do until' is executed while the other thread is running in the background. It doesn't wait for $this.BackgroundWorker to finish
        do {
            $keyPressed = [System.Console]::ReadKey().Key 
        } until ($keyPressed -eq [System.ConsoleKey]::Escape)

        # Set the $finishCountingThread variable to stop counting
        $this.finishCountingThread = $true
        if ($this.BackgroundWorker.WorkerSupportsCancellation -and $this.BackgroundWorker.IsBusy) {
            # Cancel the background worker work
            $this.BackgroundWorker.CancelAsync()
            # while loop only needed if there is no GUI or anything else preventing PS from continuing and trying to exit
            while ($this.BackgroundWorker.IsBusy) {
                # Do nothing. Block calling thread and wait for the Background worker to finish cancelling
                # Otherwise, Powershell will continue to execute code further and it can
                # end code execution while there is another thread in the background
                # That will cause an exception and a crash
                # GUIs usually prevent PowerShell from exiting
                # for which this may not be needed in a Form or WPF application
            }
        }
    }

    [Void] MethodToBeCalledFromAnotherThread([Object]$sender, [System.ComponentModel.DoWorkEventArgs]$e) {
        # Get the current thread id
        $currentThreadID = [System.Threading.Thread]::CurrentThread.ManagedThreadId
        # Say hello from the thread this method is running on
        Write-Host "Hello from " -NoNewline
        Write-Host "Thread $currentThreadId" -ForegroundColor Blue
        # Count from 0 to 10000
        for ($i = 0; $i -le 10000; $i++) {
            # Check boolean to know whether to finish counting or not
            if($this.finishCountingThread) {
                Write-Host "`nFinishing spawned " -NoNewline
                Write-Host "Thread $currentThreadID..." -ForegroundColor Blue
                break 
            }
            # Print on the console the current value of $i
            Write-Host "`rPress ESC to stop spawned " -NoNewline
            Write-Host "Thread $currentThreadID " -NoNewline -ForegroundColor Blue
            Write-Host "and quit program: " -NoNewline 
            Write-Host "[Counting $i in Thread $currentThreadID] " –NoNewline -ForegroundColor Blue
            Write-Host "Keys are being listened to on " -NoNewline
            Write-Host "Thread $($this.ClassThreadId)" -NoNewline -ForegroundColor Green
        }

        # Check if the counting finished before the user pressed Escape
        if (-Not $this.finishCountingThread) {
            "`nCounting finished" | Out-Host
            "Press ESC to quit program..." | Out-Host
        }
    }
}
# Set cursor visible to false to see colors without a jittering cursor
[System.Console]::CursorVisible = $false
"`n`n" | Out-Host
$ThreadingTestClassObject = [ThreadingTestClass]::new()
"`n`n" | Out-Host

推荐阅读