【问题标题】:C# Monitor.Enter SynchronizationLockExceptionC# Monitor.Enter SynchronizationLockException
【发布时间】:2015-06-26 17:03:26
【问题描述】:

我正在开发一个 C# 4.5 应用程序,其中许多线程需要访问同一个串行端口。

由于我也将在串行端口上接收响应,因此我想避免进一步写入,直到我收到响应并阅读它。

我有一个 SerialHandler 类,它是 SerialPort 类的包装器,写入和读取方法如下所示:

public void write(string message)
{
  Monitor.Enter(lockingObject);
  //Stuff
  serialPort.write(message);
}

public string read()
{
  //Reading procedure
  Monitor.Exit(lockingObject);
}

但是,一旦我从表单发出写入消息,我就会收到 SynchronizationLockException,说“对象同步方法是从未同步的代码块中调用的”。 lockObject 是一个私有且只读的 Object 变量。

我怎样才能避免这个错误? 非常感谢

编辑,它应该像这样工作:
线程 A 获得读/写访问权限
线程 B 尝试访问但失败
线程 A 开始读取,因此他释放了他的锁
线程 B 现在可以执行写入、获取锁、读取和移除锁

【问题讨论】:

  • 为什么直接用monitor代替lock?
  • 因为我需要在一个方法中锁定访问并在另一个方法中释放它
  • 异常说你在调用 write() 之前调用了 read()。这是一个错误。

标签: c# multithreading serial-port synchronization thread-safety


【解决方案1】:

抛出异常是因为不同的线程是trying to release the object

这是解决您问题的简单方法:

class MySerialPort
{
    private AutoResetEvent _waithandle = new AutoResetEvent(true);
    private SerialPort _serialPort;

    public void write(string message)
    {
        _waithandle.WaitOne();
        //Stuff
        _serialPort.Write(message);
    }

    public string read()
    {
        try
        {
           ...
           ...
        }
        finally
        {
            _waithandle.Set();
        }
    }

}

第一个线程将写入,​​然后任何新的写入操作将被阻塞,直到读取释放其中一个。

编辑

我在下面的 cmets 中看到您正在寻找最快的解决方案。 根据link,下面的代码将在 60ns(在 Intel Core i7 860 上)完成这项工作,这要好得多

class MySerialPort
{
    private ManualResetEventSlim _waithandle = new ManualResetEventSlim(true);
    private SerialPort _serialPort;
    private readonly object _sync = new object();

    public void write(string message)
    {
        lock (_sync)
        {
            _waithandle.Wait();
            //Stuff
            _serialPort.Write(message);
            _waithandle.Reset();
        }

    }

    public string read()
    {
        try
        {
         ....
        }
        finally
        {
            _waithandle.Set();
        }
    }
}

【讨论】:

  • 我有一个问题,如果 waitCounter 为 0,WaitOne 将被调用。这是注定要发生的吗?既然没有人会调用Set,它不会什么都不等待吗?
  • Interlocked.Increment返回增加后的值,所以不会阻塞第一个线程......
  • 哦,我明白了,非常感谢!所以它会一直等待,除非值为 0
  • @MarcoBellan 是的,它一直等到值为 0。现在我正在使用带有 VS 的计算机。我忘了你用初始化状态初始化 WaitHandles。所以你不需要计数器......我看到你正在寻找最快的解决方案,所以我用用户模式同步更新了我的答案。顺便说一句,请将答案标记为接受,因此该问题将不再有效。
  • 对不起,我昨天出去了,我看不懂新代码 为什么在写过程中需要锁?在这种情况下,Reset 做了什么,为什么读取时没有锁定?
【解决方案2】:

你可以使用互斥锁

class SerialHandler 
{
    private Mutex _mutex = new Mutex();
    private SerialPort _serialPort;
    public void write(string message)
    {
      _mutex.WaitOne();
      //Stuff
      _serialPort.write(message);
    }

    public string read()
    {
      //Reading procedure
      _mutex.ReleaseMutex();
    }
}

问题意味着您尝试释放未获取的锁。

【讨论】:

  • 哦,我相信他们会的,他们的工作方式不完全像“lock”关键字吗?我从来没有遇到过这些锁问题。这是我第一次使用 Monitor tho,而且我对多线程还比较陌生,无论如何感谢您的回答!
  • lock 必须由一个同步的代码块使用,因此根据定义,它由同一线程使用。 Monitor.Enter 和 Monitor.Exit 在这种情况下被调用,但它们总是在同一个线程中调用。
  • 好吧,我需要它被同一个线程调用:类似于: 线程 A 获得读/写访问权限 线程 B 尝试获得访问权限但失败 线程 A 获得读取权限,因此他释放了他的锁定线程B 现在可以执行写入、获取锁、读取和移除锁
  • 我认为操作流程是 a) 将行标记为“忙” b) 写入端口 c) 读取结果 d) 将行标记为空闲。正如第一篇文章中所建议的那样。
  • @RyszardFiński 是的。它需要非常快的代码,因为会有大量调用,Mutex 似乎更慢,如果我错了,请纠正我
猜你喜欢
  • 1970-01-01
  • 2014-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-08
  • 1970-01-01
  • 2012-06-28
相关资源
最近更新 更多