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 объект, содержащий дочерние задания для каждого входного значения канала. Объект задания содержит полезные сведения о состоянии выполнения дочерних заданий. Он собирает результаты дочерних заданий по мере создания результатов.

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

Можно использовать командлеты заданий 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

См. также