While debugging an application that uses Semaphores for cross-process synchronization, I stumbled upon the idea of using PowerShell to take the place of the "other" process. Doing something like this in PowerShell works fine:
// In C# application:
var sem = new Semaphore(0, 1, "CrossProcSem");
sem.WaitOne();
# In PowerShell session:
[1] C:\Projects $ $sem = New-Object System.Threading.Semaphore(0, 1, "CrossProcSem")
[2] C:\Projects $ $sem.Release()
And I can call WaitOne()
and Release()
repeatedly on that same instance of a Semaphore, as often as I need to.
But when I try to do the same thing with a Mutex, PowerShell keeps claiming that the mutex was abandoned:
[1] C:\Projects $ $mtx = New-Object System.Threading.Mutex($false, "CrossProcMtx")
[2] C:\Projects $ $mtx.WaitOne()
True
[3] C:\Projects $ $mtx.ReleaseMutex()
[4] C:\Projects $ $mtx.WaitOne()
Exception calling "WaitOne" with "0" argument(s): "The wait completed due to an abandoned mutex."
At line:1 char:13
+ $mtx.WaitOne <<<< ()
+ CategoryInfo : NotSpecified: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : DotNetMethodException
The error seems to happen any time I call WaitOne()
after having acquired the mutex once before, either a previous WaitOne
call or asking for it to be initially owned in the constructor:
[5] C:\Project开发者_StackOverflows $ $mtx2 = New-Object System.Threading.Mutex($true)
[6] C:\Projects $ $mtx2.WaitOne()
Exception calling "WaitOne" with "0" argument(s): "The wait completed due to an abandoned mutex."
At line:1 char:14
+ $mtx2.WaitOne <<<< ()
+ CategoryInfo : NotSpecified: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : DotNetMethodException
[7] C:\Projects $ $mtx3 = New-Object System.Threading.Mutex
[8] C:\Projects $ $mtx3.WaitOne()
True
[9] C:\Projects $ $mtx3.WaitOne()
Exception calling "WaitOne" with "0" argument(s): "The wait completed due to an abandoned mutex."
At line:1 char:14
+ $mtx3.WaitOne <<<< ()
+ CategoryInfo : NotSpecified: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : DotNetMethodException
Is Powershell doing some wierd thread shenanigans in the background or am I just completely forgetting how Mutexes work?
By default, powershell v2.0 (at the console, not the graphical ISE) uses an MTA threadpool. What this means is that each interactive line is executed on a different thread:
PS> [threading.thread]::CurrentThread.ManagedThreadId
13
PS> [threading.thread]::CurrentThread.ManagedThreadId
10
PS> [threading.thread]::CurrentThread.ManagedThreadId
8
PS> [threading.thread]::CurrentThread.ManagedThreadId
4
However, a non-interactive script will run under a single thread, that is to say, the thread that invoked the command to run it:
PS> $script = {
>> [threading.thread]::CurrentThread.ManagedThreadId
>> [threading.thread]::CurrentThread.ManagedThreadId
>> [threading.thread]::CurrentThread.ManagedThreadId
>> [threading.thread]::CurrentThread.ManagedThreadId
>> }
>>
PS> & $script
16
16
16
16
If you want to run powershell interactively with a single thread, start the shell with the -STA switch. You can do this interactively:
PS> powershell -sta
Windows PowerShell
Copyright (C) 2009 Microsoft Corporation. All rights reserved.
PS> $host.runspace | select threadoptions, apartmentstate
ThreadOptions ApartmentState
------------- --------------
ReuseThread STA
As you can see, powershell will use a single-threaded apartment to execute interactive commands. This is usually the desired choice for working with WPF or WinForms, or if you want to play with system-wide mutexes:
PS> $mtx = New-Object System.Threading.Mutex($false, "CrossProcMtx")
PS> $mtx.WaitOne()
True
PS> $mtx.ReleaseMutex()
PS> $mtx.WaitOne()
True
Btw, powershell v3 (shipping with windows 8 and also available downlevel on win 7) uses -STA as the default mode for the console. The graphical powershell ISE always uses -STA, both in v2 and v3.
Using this thread as inspiration, built a simple implementation of a process locking mechanism using Mutexes in powershell:
function Wait-OnMutex
{
param(
[parameter(Mandatory = $true)][string] $MutexId
)
try
{
$MutexInstance = New-Object System.Threading.Mutex -ArgumentList 'false', $MutexId
while (-not $MutexInstance.WaitOne(1000))
{
Start-Sleep -m 500;
}
return $MutexInstance
}
catch [System.Threading.AbandonedMutexException]
{
$MutexInstance = New-Object System.Threading.Mutex -ArgumentList 'false', $MutexId
return Wait-OnMutex -MutexId $MutexId
}
}
##
## main method
$MutexInstance = Wait-OnMutex -MutexId 'SomeMutexId12345'
Write-Host "my turn"
#this is where you do work inside the "lock"
Read-Host
$MutexInstance.ReleaseMutex()
Hope This helps somebody.
精彩评论