65.9K
CodeProject 正在变化。 阅读更多。
Home

抽象锁模式,用于选择性地同步实现(“IOC”和 DRY)

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.90/5 (7投票s)

2018年8月30日

CPOL

3分钟阅读

viewsIcon

14299

downloadIcon

70

这演示了一个简单的模式,它提供了一个始终可以调用的锁;并且对于非同步实现来说,它可能是一个空操作。

引言

给定一些实现——例如,通过一个接口(像一个集合)——其中“IsSynchronized”定义了实现类型——该实现必须是同步的或不是——这个简单的模式插入一个“真正的”锁,或者一个空操作。

结果是*单个*实现,具有同步或非同步的行为。

锁被抽象成一个接口;并且在这一点上,它甚至可以被检测:实际的锁实现可以提供内部检查,甚至在单个已实现的实例中随着时间的推移而改变。

给定的例子很简单:该实现需要一个简单的“块”锁定方案;但是,可以根据需要重新定义锁接口,并且您可以实现其他类型的锁定,包括可重入选择、退出并重新进入、尝试进入等等。

Using the Code

该模式的示例用例是,再次,为可以实现为“同步”或“非同步”的接口提供实现。 *单个*实现**总是**插入一个锁;在这个简单的例子中,对于非同步实现来说,这只是一个*空操作*。

例如,这个接口

public interface IBag
{
    bool IsSynchronized { get; }

    void Invoke();
}

该接口指定是否IsSynchronized;在这里我们可以使用单个实现来实现两者

public class Bag
{
    private readonly ISyncLock syncLock;


    /// <summary>
    /// Constructor.
    /// </summary>
    public Bag(bool isSynchronized)
    {
        syncLock = isSynchronized
                ? new SyncLock()
                : NoOpSyncLock.Instance;
        IsSynchronized = isSynchronized;
    }


    public bool IsSynchronized { get; }

    public void Invoke()
    {
        // Lock the selected implementation:
        using (syncLock.Enter()) {
            // Act ...
        }
    }
}

简单的实现*总是*在关键部分调用一个锁,但是所选的锁实际上可能只是一个空操作。

这个简单的“块”同步只是一个例子:锁定要求可能更复杂,但是,锁定实现仍然可以通过单个接口始终插入,并且只需执行一个空操作,或者也可以更改或检测以改变实现。 如果可能,您现在可以将锁定要求**推入**锁定类中,并让您的实现仅遵循该模式。

如示例代码所示,锁定对象甚至可以在可以参与该实现的内部类之间共享。

因此……该实现是 IOC,并且是 DRY。

ISyncLock 实现是这样的

/// <summary>
/// Defines a Monitor that is entered explicitly, and
/// exited when a returned <see cref="IDisposable"/> object is Disposed.
/// </summary>
public interface ISyncLock
{
    /// <summary>
    /// Enters the Monitor. Dispose the result to exit.
    /// </summary>
    IDisposable Enter();
}

/// <summary>
/// Implements <see cref="ISyncLock"/> with <see cref="Monitor.Enter(object)"/>
/// and <see cref="Monitor.Exit"/>.
/// </summary>
public sealed class SyncLock
        : ISyncLock,
                IDisposable
{
    private readonly object syncLock;


    /// <summary>
    /// Constructor.
    /// </summary>
    /// <param name="syncLock">Optional: if null, <see langword="this"/>
    /// is used.</param>
    public SyncLock(object syncLock)
        => this.syncLock = syncLock ?? this;


    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public IDisposable Enter()
    {
        Monitor.Enter(syncLock);
        return this;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Dispose()
        => Monitor.Exit(syncLock);
}

/// <summary>
/// Implements a do-nothing <see cref="ISyncLock"/>.
/// </summary>
public sealed class NoOpSyncLock
        : ISyncLock,
                IDisposable
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public IDisposable Enter()
        => this;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Dispose() { }
}

NoOpSyncLock 应该非常便宜。

请注意,您**不**锁定 ISyncLock 对象;*并且*您*不直接 Dispose*它。

使用 Enter 方法获取正确的 IDisposable

该细节确保了正确的**封装**。

这**也是**为什么 ISyncLock 接口不扩展 IDisposable

……除了提供不同的实现之外,您还可以增强或修改该接口。

扩展

请注意,可以检测锁定实现。 就像现在一样,如果您想使用 TryEnter 进入并可选择抛出异常,则可以在一个地方更改它

    public IDisposable Enter()
    {
        if (Monitor.TryEnter(syncLock, myTimeout))
            return this;
        throw new TimeoutException("Failed to acquire the lock.");
    }

您可以自由地使用 TryEnter 方法增强该接口

    public IDisposable TryEnter(out bool gotLock)
    {
        if (Monitor.TryEnter(syncLock, myTimeout)) {
            gotLock = true;
            return this;
        }
        gotLock = false;
        return NoOpSyncLock.Instance;
    }

然后,您的实现**必须**检查 out gotLock 结果;并且不执行任何不安全的操作——No-Op 锁只会始终返回 true。

您可以根据您的需要定义接口;并在您的实现中遵循相同的逻辑。

可以进行更多实现。 using 惯用语*也可以*让您在同一块中安全地退出并重新进入锁——例如,实现可以在进入时使用计数器和/或令牌;并且 Dispose 方法可以安全地每次退出。 也可以实现一个 async 锁……重点是锁*接口*提供了所需的单一模式来包装您需要的实现;并且可以在不需要同步的地方进行交换和存根,或者,*无法*实现……并且如上所述,可以在可以实现它的内部代码之间共享单个锁定实现。

关注点

  • 可以检测锁定实现,并全局更改。
  • 完整的实现选择器是在一个类中实现的……DRY。
  • 该实现现在正在使用控制反转。
  • 空操作的运行时成本应该非常便宜。

附加代码

有一个简单的示例附加了一个人为的用例;这说明了这一点。

历史

  • 2018年8月30日:初始版本
© . All rights reserved.