C# c语言中的锁定错误#

C# c语言中的锁定错误#,c#,multithreading,locking,serial-port,C#,Multithreading,Locking,Serial Port,我正在尝试用c#编写一个请求-响应通信模块,使用串行端口。这个 这是一个非常简单的实现,只是为了证明它可以正常工作(SerialPort不能正常工作(它是一个USB虚拟COM端口),有时会吃掉几个字符,可能是一些windows驱动程序错误) 但是,演示不起作用:-/ 在窗体上使用propertygrid读取对象的属性,然后发送从远程设备读取属性的请求时,会发生一些非常奇怪的事情:一次对SendCommand进行多个同时调用 我尝试使用lock{}块使调用顺序化,但它不起作用。即使使用锁,也会有多

我正在尝试用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,它们之间没有出口?怎么可能呢?

字段
sp
分配在哪里?锁定仅适用于非空对象

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

private static readonly object _lockObject = new object();

编辑:根据其他答案中的注释,我现在看到您实际上在UI线程上运行此逻辑,这导致在消息队列被抽取时在同一线程(UI线程)上多次重新输入锁。在不同的线程上运行此代码,您将获得两个优势:(1)当此可能长期运行的代码执行时,UI不会锁定;(2)锁定将始终在新线程上获得,从而确保对
SendCommand
的后续调用都将在各自的线程上进行,然后根据需要按顺序输入锁。

有两件事你应该尝试/更改:

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


2.应用双锁检查:

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

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

为了遵循STA的要求,每当UI线程上运行的代码执行阻塞操作时,CLR都会执行一些特殊的操作,就像SerialPort.ReadLine()一样。它泵送一个消息循环,以确保Windows发送的消息不断被发送。该消息循环的作用与Application.Run()的作用相同

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

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

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");
          }
      }

  }

我不确定它是否解决了这个问题,但通常来说,锁定与sp对象无关的私有对象(例如,
private object locker;
)会更安全,以避免(在本例中)锁定
sp
对象的任何可能的内部用途。对此进行了更改,但情况没有改变。谢谢你的提示!SP是一个(私有)类对象,它是在构造函数中分配的,也尝试使用一个单独的、私有的、静态的、只读的对象,但情况是一样的。能否提供更多上下文,例如从何处调用SendCommand以及如何启动多个线程?不幸的是,我无法影响在哪个线程上运行它,当propertygrid访问我的对象的属性(在UI线程ofc上)时,这些属性调用send命令作为副作用。我可能需要重新组织我的代码。即使我在一个新线程上调用SendCommand调用,我仍然需要阻塞UI线程,以便等待SendCommand返回的答案。public int XXX{get{return SendCommand(“此处的某些数据”);}}}如另一条评论中所述,您绝对不应该从属性调用此类方法,尤其不应该从属性getter调用此类方法。尝试了第一个方法,没有任何变化。我不确定,如何应用第二个建议,因为在任何情况下我都必须执行操作,但要等待当前操作首先完成。实际上,调用SendMessage的代码是属性访问器(getter),它构建了访问属性的命令体,并返回SendMessage返回的任何内容。基本上,我的对象只是一个前端,用来隐藏SendMessage必须如何参数化。我想这并不是一个简单的解决方案,正如你所建议的那样……但是如果你说的是真的(在阻止UI线程的情况下,消息队列会被压缩),那么如何在需要进行网络传输的对象上使用propertyGrids(在我们的例子中,SerialPort就是“网络”)呢为了计算它们的性质,让我们尽可能简单,基本问题已经足够复杂了。一个强有力的.NET设计指南规则是,属性应该具有很少的副作用。如果属性getter执行任何非平凡的操作,那么应该将其转换为方法。你完全违反了这个规则,当你的属性getter被使用时,有大量的代码在运行。也不仅仅是微妙的。它非常慢,它可以抛出异常,它不会