我有一个类ComputationService
,它是旧的遗留COM对象的包装器。由于COM对象的实现方式,该对象不能有多个实例。在应用程序中,当单击使用ComputationService
的新Task
运行时,有两个按钮(为了简化(。这些任务是一种长期运行的任务,因此当第一个按钮在计算过程中时,您可以单击第二个按钮。这就是问题发生的地方,因为我不能让两个线程都使用ComputationService
,所以我决定使用Mutex一次只允许ComputationService
的一个实例。
我所拥有的:
public class ComputationService : IComputationService
{
public ComputationService()
{
_mutex = new Mutex(true, "AwesomeMutex");
_mutex.WaitOne();
}
public void Dispose()
{
_mutex.ReleaseMutex();
}
}
然后我在两个地方使用它:
public Task Compute1()
{
return Task.Run(() =>
{
foreach (var item in items)
{
using (var service = _computationFactory()) // new instance of ComputationService
{
//Do some work
}
}
});
}
public Task Compute2()
{
return Task.Run(() =>
{
foreach (var item in items)
{
using (var service = _computationFactory()) // new instance of ComputationService
{
//Do some other work
}
}
});
}
当我调试到控制台时,我得到了什么:
Mutex created by Thread: 18
Wait, thread: 18
Do Work, thread: 18
Mutex created by Thread: 20
Wait, thread: 20
Mutex release by thread: 18
Mutex created by Thread: 18
Wait, thread: 18
Do Work, thread: 18
Mutex release by thread: 18
当然,AbandonedMutexException()
出现在线程20上。
我尝试在ComputationService
的构造函数中使用lock
,但没有成功。
你能帮我吗?我做错了什么?如何解决?
问题就在这里:
_mutex = new Mutex(true, "AwesomeMutex");
_mutex.WaitOne();
您将传递true
作为第一个参数,名为initiallyOwned
。如果你在那里传递true-这意味着具有这样名称的If互斥体以前不存在,并且是由这个构造函数创建的-当前线程现在拥有它。你不会检查互斥体是否是由这个调用创建的,实际上在使用这个构造函数重载时,甚至没有办法检查。因此,如果使用此构造函数,永远不要传递true。
如果您想使用initiallyOwned
,那么使用另一个重载,它实际上告诉您是否创建了互斥对象作为这个构造函数的结果:
_mutex = new Mutex(true, "AwesomeMutex", out var createdNew);
现在,如果createdNew
返回true,则不需要调用_mutex.WaitOne()
,因为如上所述,当前线程已经拥有互斥对象。这就是您的情况——有时您已经拥有互斥对象,然后调用_mutex.WaitOne()
。现在要发布它,你必须调用Release()
两次,而你从来没有这样做过
或者,只需传递false,然后等待:
_mutex = new Mutex(false, "AwesomeMutex");
_mutex.WaitOne();
如果整件事都发生在同一个过程中,那么你就不需要全局互斥(因为你传递了一个名称,所以现在有了全局互斥(——你可以用本地互斥做得很好:
public class ComputationService : IDisposable {
// one static instance
private static readonly Mutex _mutex = new Mutex();
public ComputationService()
{
_mutex.WaitOne();
}
public void Dispose() {
_mutex.ReleaseMutex();
}
}
还有其他可以改进的地方,但它们超出了这个问题的范围。
如评论中所述,在单个过程中,lock
(Monitor.Enter
\Monitor.Exit
(将更快,并在您的情况下获得相同的结果:
public class ComputationService : IDisposable {
// one static instance
private static readonly object _mutex = new object();
public ComputationService()
{
Monitor.Enter(_mutex);
}
public void Dispose() {
Monitor.Exit(_mutex);
}
}