我正在编写一些脆弱的多线程代码,并且有一部分代码必须不同时运行。甚至不在lock
语句中,否则我的应用程序将处于不良状态。
我想尽可能高效优雅地检查这一点。我知道我可以抛出一个跟踪线程和锁的类,但这有点像霰弹枪。在.Net / C#中执行此操作的最佳方法是什么?
问题:是否可以保护实例方法,以便在存在并发访问时抛出异常?
另外,如果重要的话,我会定位可移植类库。
答案 0 :(得分:2)
您可以使用Monitor.TryEnter
检测是否已采用lock
对象:
public static object syncRoot = new object();
public void X()
{
bool acquiredLock = false;
try
{
Monitor.TryEnter(syncRoot, ref wasLockTaken);
if (acquiredLock)
{
// If we're here, the lock was taken.
}
}
finally
{
if (acquiredLock)
Monitor.Exit(syncRoot);
}
}
如果是,TryEnter
将立即返回false
,你可以在里面做任何你想做的事。
修改强>
由于您说重新存在可能是一个问题,您可以使用SpinLock
作为provided by @MatthewWatson's answer,或使用Semaphore
:
public static object syncRoot = new object();
public static Semaphore semaphore = new Semaphore(1, 1);
public static void Main()
{
try
{
if (!semaphore.WaitOne(0))
{
// If we're here, the lock was taken.
}
}
finally
{
semaphore.Release();
}
}
答案 1 :(得分:1)
如果您想要防止来自同一线程的重入调用以及来自多个线程的并发调用,您可以使用SpinLock
。
这将防止并发和重入的呼叫。
这是一个示例程序。由于检测到多线程访问,它将引发异常。
如果您更改注释所指示的行,则会因检测到重入调用而引发其他异常。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
class Program
{
static SpinLock locker = new SpinLock(enableThreadOwnerTracking:true);
static void Main()
{
var task1 = Task.Run(() => test(0)); // Change to test(1) to test re-entrant protection.
var task2 = Task.Run(() => test(0));
Console.WriteLine("Started threads. Waiting for them to exit.");
Task.WaitAll(task1, task2);
Console.WriteLine("Waited for threads to exit.");
Console.ReadLine();
}
static void test(int n)
{
bool lockTaken = false;
try
{
locker.TryEnter(0, ref lockTaken);
if (lockTaken)
{
if (n > 0)
test(n - 1);
Thread.Sleep(1000);
}
else
{
Console.WriteLine("Throwing exception");
throw new InvalidOperationException("Error: Multithreaded access to test() detected");
}
}
finally
{
if (lockTaken)
locker.Exit();
}
}
}
}
答案 2 :(得分:0)
有一种方法可以完全避免所有锁定:
int _countTaken = 0;
void Method()
{
Interlocked.Increment(ref _countTaken);
try
{
if (_countTaken > 1)
throw new Exception(...);
DoStuff();
}
finally
{
Interlocked.Decrement(ref _countTaken);
}
}
保证不会让两个方法同时运行DoStuff()
,但有一个缺点,如果两个线程同时调用Method()
,则有可能两个线程都抛出而不是仅仅抛出第二个。