【问题标题】:How to ensure that part of code (which contains async) would be called only once?如何确保代码的一部分(包含异步)只被调用一次?
【发布时间】:2013-08-20 11:14:33
【问题描述】:

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

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

一开始,我尝试使用 bool isBusy 来锁定(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 次吗?

【问题讨论】:

  • 您使用的是什么第三方工具?

标签: c# multithreading windows-phone-7 thread-safety


【解决方案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 并退出方法

【讨论】:

  • 很好的答案,谢谢。我也可以使用 Exchange() 而不是 Increment() 吗?我需要存储有关当前页面的一些信息,因此它看起来是存储该信息的好地方。
  • @VitaliiVasylenko 不,您需要使用if (Interlocked.CompareExchange(ref entered, 1, 0) != 0) return;。第一个进入的是能够从 0 变为 1 并接收旧值 (0)。其他的更改失败并接收当前值 (1)。
  • 嗯.. 无论如何,谢谢你的回答 - 现在我知道了,在哪里可以获取更多信息。
  • @VitaliiVasylenko 从技术上讲,CompareExchange 更安全,因为在 40 亿次调用后,Interlocked.Increment 将返回 0 并成功。
【解决方案2】:

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

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

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

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

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 类的轻量级版本,会产生大约四分之一的开销。满足你的需求就足够了。

我希望这会有所帮助。

【讨论】:

  • 没有理由不能在 async 方法中使用它们。
猜你喜欢
  • 2011-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-01
  • 2020-06-01
  • 2010-11-01
  • 1970-01-01
相关资源
最近更新 更多