如何使用Mutex解决可能的种族条件



我有一个类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);
}
}

相关内容

  • 没有找到相关文章

最新更新