Skip to content

Commit efaa20d

Browse files
authored
Restore NoRunspaceAffinity info (#10271)
1 parent c542bce commit efaa20d

File tree

1 file changed

+78
-1
lines changed

1 file changed

+78
-1
lines changed

reference/7.4/Microsoft.PowerShell.Core/About/about_Classes.md

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
description: Describes how you can use classes to create your own custom types.
33
Locale: en-US
4-
ms.date: 07/05/2023
4+
ms.date: 07/13/2023
55
online version: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_classes?view=powershell-7.4&WT.mc_id=ps-gethelp
66
schema: 2.0.0
77
title: about Classes
@@ -838,6 +838,83 @@ class MyComparableBar : bar, System.IComparable
838838
}
839839
```
840840

841+
## NoRunspaceAffinity attribute
842+
843+
A runspace is the operating environment for the commands invoked by PowerShell.
844+
This environment includes the commands and data that are currently present, and
845+
any language restrictions that currently apply.
846+
847+
By default, a PowerShell class is affiliated with the **Runspace** where it's
848+
created. Using a PowerShell class in `ForEach-Object -Parallel` is not safe.
849+
Method invocations on the class are marshalled back to the **Runspace** where
850+
it was created, which can corrupt the state of the **Runspace** or cause a
851+
deadlock.
852+
853+
Adding the `NoRunspaceAffinity` attribute to the class definition ensures that
854+
the PowerShell class is not affiliated with a particular runspace. Method
855+
invocations, both instance and static, use the **Runspace** of the running
856+
thread and the thread's current session state.
857+
858+
The attribute was added in PowerShell 7.4.
859+
860+
### Example - Class definition with Runspace affinity
861+
862+
The `ShowRunspaceId()` method of `[UnsafeClass]` reports different thread Ids
863+
but the same runspace Id. Eventually, the session state is corrupted causing
864+
an error, such as `Global scope cannot be removed`.
865+
866+
```powershell
867+
# Class definition with Runspace affinity (default behavior)
868+
class UnsafeClass {
869+
static [object] ShowRunspaceId($val) {
870+
return [PSCustomObject]@{
871+
ThreadId = [Threading.Thread]::CurrentThread.ManagedThreadId
872+
RunspaceId = [runspace]::DefaultRunspace.Id
873+
}
874+
}
875+
}
876+
$unsafe = [UnsafeClass]::new()
877+
while ($true) {
878+
1..10 | ForEach-Object -Parallel {
879+
Start-Sleep -ms 100
880+
($using:unsafe)::ShowRunspaceId($_)
881+
}
882+
}
883+
```
884+
885+
> [!NOTE]
886+
> This example runs in an infinite loop. Enter <kbd>Ctrl</kbd>+<kbd>C</kbd> to
887+
> stop the execution.
888+
889+
### Example - Class definition with NoRunspaceAffinity
890+
891+
The `ShowRunspaceId()` method of `[SafeClass]` reports different thread and
892+
Runspace ids.
893+
894+
```powershell
895+
# Class definition with NoRunspaceAffinity attribute
896+
[NoRunspaceAffinity()]
897+
class SafeClass {
898+
static [object] ShowRunspaceId($val) {
899+
return [PSCustomObject]@{
900+
ThreadId = [Threading.Thread]::CurrentThread.ManagedThreadId
901+
RunspaceId = [runspace]::DefaultRunspace.Id
902+
}
903+
}
904+
}
905+
$safe = [SafeClass]::new()
906+
while ($true) {
907+
1..10 | ForEach-Object -Parallel {
908+
Start-Sleep -ms 100
909+
($using:safe)::ShowRunspaceId($_)
910+
}
911+
}
912+
```
913+
914+
> [!NOTE]
915+
> This example runs in an infinite loop. Enter <kbd>Ctrl</kbd>+<kbd>C</kbd> to
916+
> stop the execution.
917+
841918
## Importing classes from a PowerShell module
842919

843920
`Import-Module` and the `#requires` statement only import the module functions,

0 commit comments

Comments
 (0)