C# 中的暂定锁?

Tentative locks in C#?

假设我想允许并行执行一些代码,但需要其他代码等待所有这些操作完成。

让我们想象一下 softlock 除了 lock

public static class MySimpleCache
{
    private static readonly SynchronizedCollection<KeyValuePair<string, string>> Collection = new SynchronizedCollection<KeyValuePair<string, string>>();

    public static string Get(string key, Func<string> getter)
    {
        // Allow parallel enumerations here,
        // but force modifications to the collections to wait. 
        softlock(Collection.SyncRoot)
        {
            if (Collection.Any(kvp => kvp.Key == key))
            {
                return Collection.First(kvp => kvp.Key == key).Value;
            }
        }

        var data = getter();

        // Wait for previous soft-locks before modifying the collection and let subsequent softlocks wait
        lock (Collection.SyncRoot)
        {
            Collection.Add(new KeyValuePair<string, string>(key, data));
        }
        return data;
    }
}

C#/.NET 中是否有任何设计模式或 language/framework 功能可以以直接可靠的方式实现这一点,或者是否必须从头开始实现这一点?

我目前仅限于 .NET 3.5,我最感兴趣的是概念问题,而不是其他可能本身可以解决示例的集合。

在这种情况下,您可以使用 ReaderWriterLockSlim,它会允许多个读者,直到有人想要写入,然后它会阻止所有读者,只允许一个作者通过。

public static class MySimpleCache
{
    private static readonly SynchronizedCollection<KeyValuePair<string, string>> Collection = new SynchronizedCollection<KeyValuePair<string, string>>();
    private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();

    public static string Get(string key, Func<string> getter)
    {
        //This allows multiple readers to run concurrently.
        Lock.EnterReadLock();
        try
        {
            var result = Collection.FirstOrDefault(kvp => kvp.Key == key);
            if (!Object.Equals(result, default(KeyValuePair<string, string>)))
            {
                return result.Value;
            }
        }
        finally
        {
            Lock.ExitReadLock();
        }


        var data = getter();

        //This blocks all future EnterReadLock(), once all finish it allows the function to continue
        Lock.EnterWriteLock();
        try
        {
            Collection.Add(new KeyValuePair<string, string>(key, data));
            return data;
        }
        finally
        {
            Lock.ExitWriteLock();
        }
    }
}

但是,您可能想检查一下,当您在哪里等待获取写锁时,其他人可能已将记录输入到缓存中,在这种情况下,您可以使用 EnterUpgradeableReadLock(),这允许里面的人不受限制 EnterReadLock() 但只有一个人可以进入升级锁(而且仍然没有写锁)。当您知道您可能会写但有机会不写时,可升级锁很有用。

public static class MySimpleCache
{
    private static readonly SynchronizedCollection<KeyValuePair<string, string>> Collection = new SynchronizedCollection<KeyValuePair<string, string>>();
    private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();

    public static string Get(string key, Func<string> getter)
    {
        //This allows multiple readers to run concurrently.
        Lock.EnterReadLock();
        try
        {
            var result = Collection.FirstOrDefault(kvp => kvp.Key == key);
            if (!Object.Equals(result, default(KeyValuePair<string, string>)))
            {
                return result.Value;
            }
        }
        finally
        {
            Lock.ExitReadLock();
        }

        //This allows unlimited EnterReadLock to run concurrently, but only one thread can be in upgrade mode, other threads will block.
        Lock.EnterUpgradeableReadLock();
        try
        {
            //We need to check to see if someone else filled the cache while we where waiting.
            var result = Collection.FirstOrDefault(kvp => kvp.Key == key);
            if (!Object.Equals(result, default(KeyValuePair<string, string>)))
            {
                return result.Value;
            }


            var data = getter();

            //This blocks all future EnterReadLock(), once all finish it allows the function to continue
            Lock.EnterWriteLock();
            try
            {
                Collection.Add(new KeyValuePair<string, string>(key, data));
                return data;
            }
            finally
            {
                Lock.ExitWriteLock();
            }
        }
        finally
        {
            Lock.ExitUpgradeableReadLock();
        }
    }
}

P.S. 您在评论中提到该值可能为空,因此 FirstOrDefault() 不起作用。在那种情况下,使用扩展方法来创建 TryFirst() 函数。

public static class ExtensionMethods
{
    public static bool TryFirst<T>(this IEnumerable<T> @this, Func<T, bool> predicate, out T result)
    {
        foreach (var item in @this)
        {
            if (predicate(item))
            {
                result = item;
                return true;
            }
        }
        result = default(T);
        return false;
    }
}

//Used like
Lock.EnterReadLock();
try
{
    KeyValuePair<string, string> result;
    bool found = Collection.TryFirst(kvp => kvp.Key == key, out result);
    if (found)
    {
        return result.Value;
    }
}
finally
{
    Lock.ExitReadLock();
}