当同时访问某个方法时,.Net/C#引发异常

本文关键字:异常 Net 方法 访问 | 更新日期: 2023-09-27 18:25:47

我正在编写一些脆弱的多线程代码,其中有一部分代码必须而不是同时运行。即使在lock语句中也不能,否则我的应用程序将处于糟糕的状态。

我想尽可能高效、优雅地检查一下。我知道我可以扔一个追踪线程和锁的类,但这有点像猎枪锤钉子。在.Net/C#中,最好的方法是什么?

问题:是否可以保护实例方法,以便在存在并发访问时抛出异常?

此外,如果这很重要的话,我的目标是可移植类库。

当同时访问某个方法时,.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(),它们可能都会抛出,而不仅仅是第二个。