Поделиться через


about_Thread_Jobs

Краткое описание

Предоставляет сведения о заданиях на основе потоков PowerShell. Задание потока — это тип фонового задания, которое выполняет команду или выражение в отдельном потоке в текущем сеансовом процессе.

Подробное описание

PowerShell параллельно выполняет команды и скрипты с помощью заданий. Существует три типа заданий, предоставляемых PowerShell для поддержки параллелизма.

  • RemoteJob — Команды и скрипты выполняются в удаленном сеансе. Дополнительные сведения см. в разделе about_Remote_Jobs.
  • BackgroundJob — Команды и скрипты выполняются в отдельном процессе на локальном компьютере. См. дополнительные сведения о заданиях.
  • PSTaskJob или ThreadJob — команды и скрипты выполняются в отдельном потоке в рамках одного процесса на локальном компьютере.

Задания на основе потоков не так надежны, как удаленные и фоновые задания, так как они выполняются в одном процессе в разных потоках. Если в одном задании возникает критическая ошибка, которая завершает работу процесса, все остальные задания в процессе завершаются.

Однако задания на основе потоков требуют меньше накладных расходов. Они не используют уровень удаленного взаимодействия или сериализацию. Результирующий объект возвращается в виде ссылок на динамические объекты в текущем сеансе. Без этих издержек потоковые задания выполняются быстрее и используют меньше ресурсов, чем задания других типов.

Важно!

Родительский сеанс, создавший задание, также отслеживает состояние задания и собирает данные конвейера. Дочерний процесс задания завершается родительским процессом, когда задание достигает завершенного состояния. Если родительский сеанс завершается, все выполняемые дочерние задания завершаются вместе с дочерними процессами.

Обойти эту ситуацию можно двумя способами:

  1. Используйте для Invoke-Command создания заданий, которые выполняются в отключенных сеансах. Дополнительные сведения см. в разделе about_Remote_Jobs.
  2. Используйте Start-Process для создания нового процесса, а не задания. Дополнительные сведения см. в статье Start-Process.

Запуск заданий на основе потоков и управление ими

Существует два способа запуска заданий на основе потоков:

  • Start-ThreadJob — из модуля ThreadJob ;
  • ForEach-Object -Parallel -AsJob — функция параллелизма добавлена в PowerShell 7.0.

Используйте те же командлеты заданий, которые описаны в about_Jobs для управления заданиями на основе потоков.

Использование Start-ThreadJob

Модуль ThreadJob впервые поставляется с PowerShell 6. Его также можно установить из коллекция PowerShell для Windows PowerShell 5.1.

Чтобы запустить задание потока на локальном компьютере, используйте Start-ThreadJob командлет с командой или скриптом, заключенными в фигурные скобки ({ }).

В следующем примере запускается задание потока, которое выполняет Get-Process команду на локальном компьютере.

Start-ThreadJob -ScriptBlock { Get-Process }

Команда Start-ThreadJob возвращает ThreadJob объект , представляющий выполняющееся задание. Объект задания содержит полезные сведения о задании, включая его текущее состояние выполнения. Он собирает результаты задания по мере создания результатов.

Использование ForEach-Object -Parallel -AsJob

PowerShell 7.0 добавил в командлет новый набор ForEach-Object параметров. Новые параметры позволяют запускать блоки скриптов в параллельных потоках в качестве заданий PowerShell.

Данные можно передать в ForEach-Object -Parallel. Данные передаются в блок скрипта, который выполняется параллельно. Параметр -AsJob создает объекты заданий для каждого из параллельных потоков.

Следующая команда запускает задание, содержащее дочерние задания для каждого входного значения, переданного в команду. Каждое дочернее Write-Output задание выполняет команду с конвейерным входным значением в качестве аргумента.

1..5 | ForEach-Object -Parallel { Write-Output $_ } -AsJob

Команда ForEach-Object -Parallel возвращает объект , PSTaskJob содержащий дочерние задания для каждого входного значения, переданного по конвейеру. Объект job содержит полезные сведения о состоянии выполнения дочерних заданий. Он собирает результаты дочерних заданий по мере создания результатов.

Ожидание завершения задания и получение результатов задания

Можно использовать командлеты заданий PowerShell, такие как Wait-Job и Receive-Job , чтобы дождаться завершения задания, а затем возвращать все результаты, созданные заданием.

Следующая команда запускает задание потока, которое выполняет Get-Process команду, затем ожидает завершения команды и, наконец, возвращает все результаты данных, созданные командой .

Start-ThreadJob -ScriptBlock { Get-Process } | Wait-Job | Receive-Job

Следующая команда запускает задание, которое выполняет Write-Output команду для каждого переданного входного ввода, затем ожидает завершения всех дочерних заданий и, наконец, возвращает все результаты данных, созданные дочерними заданиями.

1..5 | ForEach-Object -Parallel { Write-Output $_ } -AsJob | Wait-Job | Receive-Job

Командлет Receive-Job возвращает результаты дочерних заданий.

1
3
2
4
5

Так как каждое дочернее задание выполняется параллельно, порядок созданных результатов не гарантируется.

Производительность задания потока

Задания потоков выполняются быстрее и легче, чем другие типы заданий. Но они по-прежнему имеют накладные расходы, которые могут быть большими по сравнению с работой, которую выполняет работа.

PowerShell выполняет команды и скрипты в сеансе. Одновременно в сеансе может выполняться только одна команда или скрипт. Поэтому при выполнении нескольких заданий каждое задание выполняется в отдельном сеансе. Каждый сеанс вносит свой вклад в издержки.

Задания потоков обеспечивают наилучшую производительность, если выполняемая ими работа превышает издержки сеанса, используемого для выполнения задания. Существует два случая, когда это соответствует этому критерию.

  • Работа ресурсоемкая. Выполнение скрипта в нескольких заданиях потоков может использовать преимущества нескольких ядер процессора и выполняться быстрее.

  • Работа состоит из значительного ожидания— скрипт, который тратит время на ожидание ввода-вывода или результатов удаленного вызова. Выполнение параллельно обычно завершается быстрее, чем при последовательном выполнении.

(Measure-Command {
    1..1000 | ForEach { Start-ThreadJob { Write-Output "Hello $using:_" } } | Receive-Job -Wait
}).TotalMilliseconds
36860.8226

(Measure-Command {
    1..1000 | ForEach-Object { "Hello: $_" }
}).TotalMilliseconds
7.1975

В первом примере выше показан цикл foreach, который создает 1000 заданий потока для простой записи строки. Из-за накладных расходов на выполнение заданий требуется более 36 секунд.

Во втором примере выполняется ForEach командлет , чтобы выполнить те же 1000 операций. На этот раз ForEach-Object выполняется последовательно в одном потоке без дополнительных затрат на задания. Он завершается всего за 7 миллисекундах.

В следующем примере для 10 отдельных системных журналов собирается до 5000 записей. Так как скрипт включает чтение нескольких журналов, имеет смысл выполнять операции параллельно.

$logNames.count
10

Measure-Command {
    $logs = $logNames | ForEach-Object {
        Get-WinEvent -LogName $_ -MaxEvents 5000 2>$null
    }
}

TotalMilliseconds : 252398.4321 (4 minutes 12 seconds)
$logs.Count
50000

Скрипт завершается в два раза, когда задания выполняются параллельно.

Measure-Command {
    $logs = $logNames | ForEach {
        Start-ThreadJob {
            Get-WinEvent -LogName $using:_ -MaxEvents 5000 2>$null
        } -ThrottleLimit 10
    } | Wait-Job | Receive-Job
}

TotalMilliseconds : 115994.3 (1 minute 56 seconds)
$logs.Count
50000

Задания и переменные потока

Существует несколько способов передачи значений в задания на основе потоков.

Start-ThreadJobможет принимать переменные, которые передаются в командлет, передаются в блок скрипта $using через ключевое слово или передаются с помощью параметра ArgumentList.

$msg = "Hello"

$msg | Start-ThreadJob { $input | Write-Output } | Wait-Job | Receive-Job

Start-ThreadJob { Write-Output $using:msg } | Wait-Job | Receive-Job

Start-ThreadJob { param ([string] $message) Write-Output $message } -ArgumentList @($msg) |
  Wait-Job | Receive-Job

ForEach-Object -Parallelпринимает передаваемые по конвейеру переменные и переменные, передаваемые непосредственно в блок скрипта $using через ключевое слово.

$msg = "Hello"

$msg | ForEach-Object -Parallel { Write-Output $_ } -AsJob | Wait-Job | Receive-Job

1..1 | ForEach-Object -Parallel { Write-Output $using:msg } -AsJob | Wait-Job | Receive-Job

Так как задания потоков выполняются в одном процессе, любые ссылочные типы переменных, передаваемые в задание, должны тщательно обрабатываться. Если это не потокобезопасный объект, его никогда не следует назначать, а метод и свойства никогда не должны вызываться.

В следующем примере потокобезопасный объект .NET ConcurrentDictionary передается всем дочерним заданиям для сбора объектов процесса с уникальными именами. Так как это потокобезопасный объект, его можно безопасно использовать во время параллельного выполнения заданий в процессе.

$threadSafeDictionary = [System.Collections.Concurrent.ConcurrentDictionary[string,object]]::new()
$jobs = Get-Process | ForEach {
    Start-ThreadJob {
        $proc = $using:_
        $dict = $using:threadSafeDictionary
        $dict.TryAdd($proc.ProcessName, $proc)
    }
}
$jobs | Wait-Job | Receive-Job

$threadSafeDictionary.Count
96

$threadSafeDictionary["pwsh"]

NPM(K)  PM(M)   WS(M) CPU(s)    Id SI ProcessName
------  -----   ----- ------    -- -- -----------
  112  108.25  124.43  69.75 16272  1 pwsh

См. также раздел