当同时访问某个方法时,.Net/C#引发异常
本文关键字:异常 Net 方法 访问 | 更新日期: 2023-09-27 18:25:47
我正在编写一些脆弱的多线程代码,其中有一部分代码必须而不是同时运行。即使在lock
语句中也不能,否则我的应用程序将处于糟糕的状态。
我想尽可能高效、优雅地检查一下。我知道我可以扔一个追踪线程和锁的类,但这有点像猎枪锤钉子。在.Net/C#中,最好的方法是什么?
问题:是否可以保护实例方法,以便在存在并发访问时抛出异常?
此外,如果这很重要的话,我的目标是可移植类库。
您可以使用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
,您可以在里面做任何您想做的事情。
编辑:
由于你说重新进入可能是个问题,你可以使用@MatthewWatson的答案提供的SpinLock
,或者使用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();
}
}
如果要防止来自同一线程的重入调用以及来自多个线程的并发调用,可以使用SpinLock
。
这将防止并发的AND重入调用。
这是一个示例程序。由于检测到多线程访问,它将引发异常。
如果更改注释指示的行,由于检测到重入调用,它将引发不同的异常。
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();
}
}
}
}
有一种方法可以完全避免所有锁:
int _countTaken = 0;
void Method()
{
Interlocked.Increment(ref _countTaken);
try
{
if (_countTaken > 1)
throw new Exception(...);
DoStuff();
}
finally
{
Interlocked.Decrement(ref _countTaken);
}
}
这保证了永远不会让两个方法同时运行DoStuff()
,但缺点是,如果两个线程同时调用Method()
,它们可能都会抛出,而不仅仅是第二个。