如何确保部分代码(包含异步代码)仅调用一次

本文关键字:代码 调用 一次 异步 何确保 确保 包含 | 更新日期: 2023-09-27 18:28:16

所以,我有一个方法,其中包含对服务器的异步调用。该代码是从第三方工具调用的,该工具有时会以某种方式从不同的线程连续多次调用相同的方法,所以我无法影响它。

我想确定的是,我的方法被调用一次,然后,另一个调用应该被忽略。

起初,我尝试用 bool isBusy(locker(锁定(locker(,但这并不满足我,因为异步请求仍然从第二个线程调用了几次,这足够快,可以看到 isBusy=true;

然后,我尝试了监视器

                object obj = new object();
                Monitor.TryEnter(obj);
                try
                {
                    var res = await _dataService.RequestServerAsync(SelectedIndex, e.StartIndex, e.Count);
                    ****
                }
                finally 
                {
                    Monitor.Exit(obj);
                }

但是,在Exit(),我得到了例外:

类型的第一次机会异常 'System.Threading.SynchronizationLockException'

有没有其他方法可以保证代码只执行 1 次?

如何确保部分代码(包含异步代码)仅调用一次

放入类:

private int entered = 0;

在方法中:

if (Interlocked.Increment(ref entered) != 1)
{
    return;
}

只有对该方法的第一次调用才能将entered从 0 更改为 1。其他人将使其成为 2、3、4、5...

显然,如果您希望

您的方法可重新触发,您将需要一些东西来重置entered......

Interlocked.Exchange(ref entered, 0);

在成功调用方法结束时。

啊。。。并且不可能在await周围使用 lock/Monitor.* ,因为该方法的当前线程可以更改,而几乎所有的同步库都希望您用于输入锁的线程与您用于退出锁的线程相同。

您甚至可以使用Interlocked.CompareExchange()...

if (Interlocked.CompareExchange(ref entered, 1, 0) != 0)
{
    return;
}

要输入的第一个线程将能够将输入的值从 0 交换为 1,并且它将接收旧值 0(因此 if 失败并继续剩余代码(。其他线程将失败CompareExchange并看到"当前"值 1,因此输入 if 并退出方法

如果您确实想同时使用相同的方法限制多个线程,那么我会使用 Semaphore 类来促进所需的线程限制; 方法如下...

信号量就像一个平均的夜总会保镖,它已经提供了俱乐部容量,不允许超过这个限制。一旦俱乐部满员,其他人就不能进入......外面排起了长队。然后当一个人离开另一个人时可以进入(类比感谢J. Albahari(。

值为 1 的Semaphore等效于MutexLock,只是Semaphore没有所有者,因此它不受线程影响。任何线程都可以在Semaphore上调用Release,而对于Mutex/Lock,只有获得Mutex/Lock的线程才能释放它。

现在,对于您的情况,我们能够使用 Semaphore 来限制并发性并防止太多线程同时执行特定代码段。在下面的示例中,五个线程尝试进入一个只允许进入三个...

class BadAssClub
{
    static SemaphoreSlim sem = new SemaphoreSlim(3);
    static void Main()
    {
        for (int i = 1; i <= 5; i++) 
            new Thread(Enter).Start(i);
    }
    // Enfore only three threads running this method at once.
    static void Enter(int i)
    {
        try
        {
            Console.WriteLine(i + " wants to enter.");
            sem.Wait();
            Console.WriteLine(i + " is in!");
            Thread.Sleep(1000 * (int)i);
            Console.WriteLine(i + " is leaving...");
        }
        finally
        {
            sem.Release();
        }
    }
}

请注意,SemaphoreSlimSemaphore 类的较轻量级版本,会产生大约四分之一的开销。 它足以满足您的要求。

我希望这有所帮助。