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

时间:2013-08-20 11:14:34

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

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

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

起初,我试图用bool isBusy来锁定(locker),但是我不满意,因为异步请求仍然从第二个线程被多次调用,这很快就能看到isBusy = true;

然后,我尝试了Monitor

                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'

还有其他方法可以保证只执行一次代码吗?

2 个答案:

答案 0 :(得分:14)

加入课堂:

private int entered = 0;

并在方法中:

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

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

如果您希望自己的方法可以被重新定位,显然您需要重置entered ...

Interlocked.Exchange(ref entered, 0);

在成功调用该方法结束时。

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

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

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

要输入的第一个线程将能够将输入的值从0更改为1,并且它将接收旧值0(因此,如果在其余代码中继续if并继续)。其他线程将失败CompareExchange并看到“当前”值为1,因此输入if和退出方法

答案 1 :(得分:3)

如果你想同时使用相同的方法限制多个线程,那么我会使用Semaphore类来促进所需的线程限制;这是怎么......

信号量就像一个卑鄙的夜总会保镖,它一直提供俱乐部容量,不允许超过这个限制。一旦俱乐部满员,没有其他人可以进入...队列在外面建立起来。然后当一个人离开时,另一个人可以进入(类比感谢J. Albahari)。

值为1的Semaphore等同于MutexLock,但Semaphore没有所有者,因此它是线程无知的。任何主题都可以在Release上调用Semaphore,而使用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类的轻量级版本,并且会产生大约四分之一的开销。它足以满足您的需求。

我希望这会有所帮助。