c#中的锁定错误

Áko*_*dra 5 c# multithreading locking serial-port

我正在尝试使用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");
              }
          }

      }
Run Code Online (Sandbox Code Playgroud)

输出:

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

EXIT
ENTER
Sending request... C02UgQARwA=
ENTER
Sending request... C02UgAABAA=
Response is: gQARwAAPHhtbD48bWVzc2FnZT5IZWxsbyBYWDIhPC9tZXNzYWdlPjxkZXN0aW5haXRvbj5NaXNpPC9kZXN0aW5hdGlvbj48L3htbD4=
Run Code Online (Sandbox Code Playgroud)

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

jam*_*eff 5

sp分配的领域在哪里?锁仅适用于非null对象.

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

private static readonly object _lockObject = new object();
Run Code Online (Sandbox Code Playgroud)

编辑: 我现在根据其他答案中的注释看到你实际上是在UI线程上运行这个逻辑,这导致在消息队列被泵送时在同一个线程(UI线程)上多次重新输入锁定.在另一个线程上运行此代码,您将获得两个优势:(1)UI将无法锁定,因为这可能是长时间运行的代码,并且(2)将始终在新线程上获取锁定,确保后续调用SendCommand将全部在他们自己的线程上,因此根据需要顺序进入锁定.


Han*_*ant 5

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

您需要知道的下一个细节是UI线程是特殊的,它是可重入的.该sp.ReadLine();调用将阻止UI线程.这是非法的,GUI程序的UI线程作为"单线程单元"运行,由程序的Main()方法的[STAThread]属性启用.STA线程的合同禁止它阻塞,这很可能导致死锁.

为了遵循STA的要求,只要在UI线程上运行的代码执行阻塞操作(如SerialPort.ReadLine()),CLR就会执行一些特殊操作.它抽取一个消息循环,以确保Windows发送的消息不断被分派.该消息循环与Application.Run()的作用相同.

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

解决这个问题并不容易,我们无法看到触发SendMessage()的代码.但是你需要以某种方式防止这种情况发生.关于此问题的更多背景知识.

  • 让我们保持尽可能简单,基本问题足够复杂.一个强大的.NET设计指南规则是属性应该具有非常少的副作用.如果属性getter执行任何非常重要的操作,那么它应该转换为方法.您在黑桃中违反了此规则,使用属性获取器时会运行大量代码.不仅仅是微妙的.它非常慢,它可以抛出异常,它需要锁定.属性getter应该*永远不会*做任何这些事情.永远不要写那样的代码,它会让你遇到麻烦. (2认同)