线程同步(锁定)仅释放到最后一个线程

时间:2015-09-02 19:16:53

标签: c# locking mutex cancellationtokensource

确保只有'last-in'线程可以访问互斥锁/锁定区域而中间线程没有获得锁定的正确方法是什么?

示例序列:

A acquires lock
B waits
C waits
B fails to acquire lock*
A releases lock
C acquires lock

* B应该无法通过异常获取锁定(如SemaphoreSlim.Wait(CancellationToken)或布尔Monitor.TryEnter()类型构造。

我可以想到几个类似的方案来实现这一点(例如使用CancellationTokenSourceSemaphoreSlim),但它们似乎都不是特别优雅。

这种情况是否有常见做法?

2 个答案:

答案 0 :(得分:2)

这应该像您想要的那样工作,它使用大小为1的SemaphoreSlim来控制它。我还添加了对传递CancelationToken以取消等待锁定的支持,它还支持WaitAsync返回任务而不是阻止。

public sealed class LastInLocker : IDisposable
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
    private CancellationTokenSource _cts = new CancellationTokenSource();
    private bool _disposed = false;

    public void Wait()
    {
        Wait(CancellationToken.None);
    }

    public void Wait(CancellationToken earlyCancellationToken)
    {
        if(_disposed)
            throw new ObjectDisposedException("LastInLocker");

        var token = ReplaceTokenSource(earlyCancellationToken);
        _semaphore.Wait(token);
    }

    public Task WaitAsync()
    {
        return WaitAsync(CancellationToken.None);
    }

    public async Task WaitAsync(CancellationToken earlyCancellationToken)
    {
        if (_disposed)
            throw new ObjectDisposedException("LastInLocker");

        var token = ReplaceTokenSource(earlyCancellationToken);

        //I await here because if ReplaceTokenSource thows a exception I want the 
        //observing of that exception to be deferred until the caller awaits my 
        //returned task.
        await _semaphore.WaitAsync(token).ConfigureAwait(false);
    }

    public void Release()
    {
        if (_disposed)
            throw new ObjectDisposedException("LastInLocker");

        _semaphore.Release();
    }

    private CancellationToken ReplaceTokenSource(CancellationToken earlyCancellationToken)
    {
        var newSource = CancellationTokenSource.CreateLinkedTokenSource(earlyCancellationToken);
        var oldSource = Interlocked.Exchange(ref _cts, newSource);
        oldSource.Cancel();
        oldSource.Dispose();

        return newSource.Token;
    }

    public void Dispose()
    {
        _disposed = true;

        _semaphore.Dispose();
        _cts.Dispose();
    }
}

这是一个重新创建测试示例的小测试程序

internal class Program
{
    static LastInLocker locker = new LastInLocker();
    private static void Main(string[] args)
    {
        Task.Run(() => Test("A"));
        Thread.Sleep(500);
        Task.Run(() => Test("B"));
        Thread.Sleep(500);
        Task.Run(() => Test("C"));
        Console.ReadLine();
    }

    private static void Test(string name)
    {
        Console.WriteLine("{0} waits for lock", name);
        try
        {
            locker.Wait();
            Console.WriteLine("{0} acquires lock", name);

            Thread.Sleep(4000);
            locker.Release();

            Console.WriteLine("{0} releases lock", name);
        }
        catch (Exception)
        {
            Console.WriteLine("{0} fails to acquire lock", name);
        }
    }
}

输出

A waits for lock
A acquires lock
B waits for lock
C waits for lock
B fails to acquire lock
A releases lock
C acquires lock
C releases lock

答案 1 :(得分:0)

试试这个:

public interface ILocker
{
    bool GetLock();

    void Release();
}

class Locker : ILocker
{
    private long m_NumberOfTimeGetLockWasCalled = 0;

    private readonly object m_LockingObject = new object();

    private readonly object m_LockingObject2 = new object();

    public bool GetLock()
    {

        long lock_count = 0;

        var lock_was_taken = false;

        lock(m_LockingObject)
        {
            lock_count = m_NumberOfTimeGetLockWasCalled++;

            lock_was_taken = Monitor.TryEnter(m_LockingObject2);

            if (lock_was_taken)
                return true;

        }

        while(!lock_was_taken)
        {

            Thread.Sleep(5);

            lock(m_LockingObject)
            {

                if (lock_count != m_NumberOfTimeGetLockWasCalled)
                    return false;

                lock_was_taken = Monitor.TryEnter(m_LockingObject2);

                if (lock_was_taken)
                    break;

            }


        }


        return true;
    }

    public void Release()
    {
        Monitor.Exit(m_LockingObject2);
    }
}