Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/visual-studio-code/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 仅释放到最后一个线程的线程同步(锁定)_C#_Locking_Mutex_Cancellationtokensource - Fatal编程技术网

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

C# 仅释放到最后一个线程的线程同步(锁定),c#,locking,mutex,cancellationtokensource,C#,Locking,Mutex,Cancellationtokensource,当中间线程不获取锁时,如何确保只有“最后一个”线程可以访问互斥锁/锁定区域 示例序列: A acquires lock B waits C waits B fails to acquire lock* A releases lock C acquires lock *B应该无法通过异常(如在信号量lim.Wait(CancellationToken)或布尔监视器.TryEnter()类型构造中)获取锁 我可以想到几个类似的方案来实现这一点(例如使用CancellationTokenSource

当中间线程不获取锁时,如何确保只有“最后一个”线程可以访问互斥锁/锁定区域

示例序列:

A acquires lock
B waits
C waits
B fails to acquire lock*
A releases lock
C acquires lock
*B应该无法通过异常(如在
信号量lim.Wait(CancellationToken)
或布尔
监视器.TryEnter()
类型构造中)获取锁

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

这种情况下有常见的做法吗?

试试以下方法:

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);
    }
}

这应该可以像您希望的那样工作,它使用大小为1的信号量LIM来控制它。我还添加了对传入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 等待锁 获得锁 B等待锁 C等待锁 B未能获得锁 开锁 C获得锁 C释放锁
这是一个有趣的问题-如果-在
A
释放锁和
C
获取锁之间-
D
出现,会发生什么?我只是好奇-试图想出一个需要这种方法的场景。对不起,我不理解“B失败”你这么说是什么意思?你从线程窗口冻结了线程?@xxbbcc期望的结果是最后一个in(在你的场景中是D)成功地获得了锁,而中间线程没有(B&C没有进入锁定部分)。@Kapoor我的意思是B没有获得锁,或者是通过异常(如
SemaphoreSlim.Wait)(CancellationToken
)或布尔值
监视器。TryEnter
类型构造。@AndrewHanlon是的,这正是我所怀疑的。但很难定义什么是“最后一个”。我猜一旦向线程授予锁的过程开始,任何新线程都是“第一个”.Hi Scott,谢谢你的详细回答。这与我一直使用的方法非常接近(只是用联锁的.Exchange替换内部锁)。但是将其包装到可重用类中是一种非常好的方法。@AndrewHanlon我想要
.Cancel()
新的令牌分配将是一个原子操作,否则我可能会做类似的操作。我只需要重新编写一点,我想我可以用联锁交换来完成,但这个锁可能会非常小,而且通常是无争用的,所以开销不会太大。我一直希望/认为应该有一个更简单的方法完成此场景非常简单,但事实上这可能是正确的方法。@AndrewHanlon这很容易,只要我想一想,我就用
Interlocked.Exchange更新了答案