5

我正在尝试使用 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 吗?这怎么可能?

4

4 回答 4

6

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

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

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

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

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

于 2012-12-25T19:26:42.023 回答
5

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

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

private static readonly object _lockObject = new object();

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

于 2012-12-25T18:25:05.880 回答
0

您应该尝试/更改两件事:

1.制作一个单独的字段,仅用于锁定

2.应用双重锁定检查:双重检查锁定

于 2012-12-25T18:33:07.137 回答
0

串行端口 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");
          }
      }

  }
于 2014-12-13T17:29:52.390 回答