在异步或同步操作中使用监视器锁

时间:2018-12-20 08:20:43

标签: c# .net async-await c#-7.0

我正在尝试提供一种解决方案,该方案以一种锁定全局资源的方式来强制锁定访问权限,并且可用于同步或异步操作。

我创建了一个通用类State:

public class State<T>
{
  public class LockedState : IDisposable {
    public LockedState(State<T> state) { this.state = state; Monitor.Enter(this.state.theLock); } // would be great if this could be private
    public void Dispose() => Monitor.Exit(this.state.theLock);
    public ref T Val { get { return ref state.t; } }

    readonly State<T> state;
  };

  public State() { }
  public State(T t) { this.t = t; }
  public LockedState Locked { get { return new LockedState(this); } }
  readonly object theLock = new object();
  T t;
}

这里的想法是我可以在Program类中拥有一个全局“商店”:

public class Program {
    static public readonly State<ImmutableList<int>> TheList = new State<ImmutableList<int>>(ImmutableList<int>.Empty);
    static public readonly State<SomeType> SomeType = new State<SomeType>(SomeType());
}

然后访问状态的唯一方法是通过获取这样的锁:

using (var lTheList = Program.TheList.Locked) {
   lTheList.Val = lTheList.Val.Add(5));
}

在我看来,它比普通锁要好得多,因为它会在获取/设置之前强制锁定(您不能忘记锁定)

(旁注这是个好策略吗?)

问题是我无法在异步代码中使用以上代码:

using (var someType = Program.SomeType.Lock()) {
   var x = await someType.Val.SomeAsyncOp();
}

我得到一个例外:Object synchronization method was called from an unsynchronized block of code

我发现这篇帖子How to protect resources that may be used in a multi-threaded or async environment?中有一个AsyncLock类,但是我不明白如何将类似AsyncLock的类放入我的StateLock类中。.

State.Lock()是否可以返回可用于同步和异步调用者的锁定状态?那才是我真正想要的!

如果不是我最好的前进方法是什么?使用SemaphoreSlim并拥有State.Lock()State.AsyncLock()吗?

谢谢!

2 个答案:

答案 0 :(得分:6)

您不能在此处使用Monitor,因为Monitor(又名lock)本质上是基于线程的,并且要求您从与相同的线程中“退出”该锁。 >获得锁-这正是您在async代码中很少期望的情况。相反,请考虑使用SemaphoreSlim,您可以从中获取和释放令牌,但令牌不是 线程绑定的。

伪代码:

// usually a field somewhere
SemaphoreSlim obj = new SemaphoreSlim(1);

// acquire lock:
obj.Wait(); // or await obj.WaitAsync() if want async acquire

// release lock:
obj.Release(); // often in a "finally"

答案 1 :(得分:-2)

您的代码令人担忧,因为任何恶意程序员都可以在lock上使用Program.TheList.Lock()而无需调用.Dispose(),从而导致将来的任何调用者陷入构造函数中。构造函数应始终快速干净地执行,以免破坏运行时。

您的代码是各种代码的味道。

最好将lambda传递到您的代码中并对其进行锁定。

尝试以下代码:

public class State<T>
{
    public State() { }
    public State(T t) { this.t = t; }

    private T t = default(T);
    private readonly object theLock = new object();

    public void With(Action<T> action)
    {
        lock (this.theLock)
        {
            action(this.t);
        }
    }

    public void Update(Func<T, T> action)
    {
        lock (this.theLock)
        {
            this.t = action(this.t);
        }
    }

    public R Select<R>(Func<T, R> action)
    {
        lock (this.theLock)
        {
            return action(this.t);
        }
    }
}

现在,与您的Program类一起,您可以执行以下操作:

Program.TheList.Update(t => t.Add(5));

代码更少,更简单。

您也可以这样做:

async void Main()
{
    var wc = new State<WebClient>(new WebClient());

    string source = wc.Select<string>(x => x.DownloadString(new Uri("http://www.microsoft.com")));

    Console.WriteLine(source);
}

我希望这会有所帮助。