【问题标题】:Locking error in c#c#中的锁定错误
【发布时间】:2012-12-25 18:21:47
【问题描述】:

我正在尝试使用 SerialPort 在 c# 中执行请求-响应通信模块。这 这是一个非常简单的实现,只是为了证明它有点工作(SerialPort 不能正常工作(它是一个 USB 虚拟 COM 端口),有时会吃掉几个字符,可能是一些 Windows 驱动程序错误)。

但是演示不起作用:-/

当在表单上使用 propertygrid 时,它会读取对象的属性,然后发送一个从远程设备读取属性的请求,会发生非常奇怪的事情:一次同时调用多个 SendCommand .

我尝试使用 lock{} 块来使调用按顺序进行,但它不起作用。即使有锁,也有不止一个呼叫进入保护区。

你能告诉我我做错了什么吗?

我的代码:

    SerialPort sp;

    public byte[] SendCommand(byte[] command)
      {
          //System.Threading.Thread.Sleep(100);
          lock (sp)
          {
              Console.Out.WriteLine("ENTER");
              try
              {

                  string base64 = Convert.ToBase64String(command);

                  string request = String.Format("{0}{1}\r", target_UID, base64);

                  Console.Out.Write("Sending request... {0}", request);

                  sp.Write(request);

                  string response;

                  do
                  {
                      response = sp.ReadLine();
                  } while (response.Contains("QQ=="));

                  Console.Out.Write("Response is: {0}", response);

                  return Convert.FromBase64String(response.Substring(target_UID.Length));
              }

              catch (Exception e)
              {
                  Console.WriteLine("ERROR!");
                  throw e;
              }
              finally
              {
                  Console.Out.WriteLine("EXIT");
              }
          }

      }

输出:

ENTER
Sending request... C02UgAABAA=
Response is: cgAABAAARwAAAA==

EXIT
ENTER
Sending request... C02UgQARwA=
ENTER
Sending request... C02UgAABAA=
Response is: gQARwAAPHhtbD48bWVzc2FnZT5IZWxsbyBYWDIhPC9tZXNzYWdlPjxkZXN0aW5haXRvbj5NaXNpPC9kZXN0aW5hdGlvbj48L3htbD4=

注意到两个 ENTER-s,它们之间没有 EXIT 吗?这怎么可能?

【问题讨论】:

  • 我不确定它是否能解决这个问题,但通常锁定与 sp 对象无关的私有对象(例如private object locker;)会更安全,以避免任何可能的内部使用锁定 (在这种情况下)sp 对象。
  • 改变了,但情况没有改变。感谢您的提示!

标签: c# multithreading locking serial-port


【解决方案1】:

您需要记住lock 关键字的作用,它只允许一个线程 进入锁。问题是,您没有使用任何线程。所有这些代码都在 UI 线程上运行,即程序的主线程。

你需要知道的下一个细节是UI线程是特殊的,它是re-entrantsp.ReadLine(); 调用将阻塞 UI 线程。这是非法的,GUI 程序的 UI 线程作为“单线程单元”运行,由程序 Main() 方法上的 [STAThread] 属性启用。 STA线程的合约禁止它阻塞,这很可能导致死锁。

为了满足 STA 的要求,每当在 UI 线程上运行的代码执行阻塞操作时,CLR 都会执行一些特殊操作,例如 SerialPort.ReadLine() 所做的。它泵送一个消息循环以确保 Windows 发送的消息不断被分派。该消息循环与 Application.Run() 所做的事情相同。

也许您可以看到它的标题,允许 PropertyGrid 再次调用您的 SendCommand() 方法。锁根本不起作用,这发生在同一个线程上。

解决这个问题并不容易,我们看不到触发 SendMessage() 的代码。但是您需要以某种方式防止这种情况发生。 this question 中有关此行为的更多背景信息。

【讨论】:

  • 实际上,调用 SendMessage 的代码是属性访问器(getter),它根据访问的属性构建命令体,并返回 SendMessage 返回的任何内容。基本上,我的对象只是一个前端,用于隐藏 SendMessage 必须如何参数化。正如你所建议的那样,我想这不会是一个简单的解决方案......
  • 但是如果你说的是真的(在阻塞 UI 线程的情况下消息队列被抽出),怎么可能在需要进行网络传输的对象上使用 propertyGrids(在我们的例子中SerialPort 是“网络”)以便计算它们的属性?
  • 让我们尽量保持简单,潜在的问题已经够复杂了。一个强大的 .NET 设计指南规则是属性应该有非常少的副作用。如果一个属性 getter 做了任何重要的事情,那么它应该被转换为一个方法。您违反了这条规则,当使用您的属性获取器时,会运行大量代码。也不仅仅是微妙的。它非常慢,它可以抛出异常,它需要一个锁。属性获取器应该从不做任何这些事情。永远不要写这样的代码,它会给你带来麻烦。
  • “永远不要写这样的代码,它会给你带来麻烦” - 是的,它就是这样 :)
  • @ÁkosVandra:如果这个(优秀的)答案不完全清楚,请参阅这个最近的问题以获得更详细的解释:stackoverflow.com/questions/13983753/…
【解决方案2】:

sp 字段分配在哪里?锁只对非空对象起作用。

如果sp 在每次调用时分配不同,则锁不会互斥(锁仅在同一个对象实例上互斥)。在这种情况下,您需要有一个静态字段用于锁定:

private static readonly object _lockObject = new object();

编辑:我现在根据其他答案中的 cmets 看到,您实际上是在 UI 线程上运行此逻辑,这导致在同一个线程上多次重新输入锁( UI 线程)当消息队列被抽取时。在不同的线程上运行此代码,您将获得两个优势:(1) UI 不会在执行此可能长时间运行的代码时锁定,以及 (2) 将始终在新线程上获取锁定,确保随后对SendCommand 的调用都将在它们自己的线程上,因此可以根据需要依次进入锁。

【讨论】:

  • SP是(私有)类对象,在构造函数中赋值
  • 也尝试使用单独的、私有的、静态的、只读的对象,但情况是一样的。
  • 能否提供更多上下文,例如从何处调用 SendCommand 以及如何启动多个线程?
  • 不幸的是,我无法影响在哪个线程上运行,因为 propertygrid 访问我的对象的属性(在 c 的 UI 线程上),它调用 SendCommands 作为副作用。我可能需要重新组织我的代码。即使我在新线程上调用 SendCommand 调用,我仍然需要阻塞 UI 线程以等待需要返回的 SendCommand 的答案。 public int XXX { get { return SendCommand("SOME DATA HERE"); } }
  • 正如另一条评论中提到的,您绝对不应该从属性调用这样的方法,尤其是不要从属性获取器调用。
【解决方案3】:

你应该尝试/改变两件事:

1.创建一个单独的字段,仅用于锁定

2.应用双重锁定检查:double check locking

【讨论】:

  • 尝试了第一个,没有任何变化。
  • 我不确定如何应用第二个建议,因为无论如何我都必须执行操作,但要先等待当前操作完成。
【解决方案4】:

SerialPort sp;

public byte[] SendCommand(byte[] command)
  {
      //System.Threading.Thread.Sleep(100);
      lock (sp)
      {
          Console.Out.WriteLine("ENTER");
          try
          {

              string base64 = Convert.ToBase64String(command);

              string request = String.Format("{0}{1}\r", target_UID, base64);

              Console.Out.Write("Sending request... {0}", request);

              sp.Write(request);

              string response;

              do
              {
                  response = sp.ReadLine();
              } while (response.Contains("QQ=="));

              Console.Out.Write("Response is: {0}", response);

              return Convert.FromBase64String(response.Substring(target_UID.Length));
          }

          catch (Exception e)
          {
              Console.WriteLine("ERROR!");
              throw e;
          }
          finally
          {
              Console.Out.WriteLine("EXIT");
          }
      }

  }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-03
    • 1970-01-01
    • 2015-09-10
    • 2014-04-16
    • 1970-01-01
    • 2015-10-06
    相关资源
    最近更新 更多