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


о_Thread_Jobs

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

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

Длинное описание

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

  • RemoteJob — команды и скрипты выполняются в удаленном сеансе. См. раздел about_Remote_Jobsдля получения дополнительной информации.
  • BackgroundJob — команды и скрипты выполняются в отдельном процессе на локальном компьютере. Дополнительные сведения см. в разделе about_Jobs.
  • 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-Object для выполнения одинаковых 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

См. также