C# c语言中的锁定错误#
我正在尝试用c#编写一个请求-响应通信模块,使用串行端口。这个 这是一个非常简单的实现,只是为了证明它可以正常工作(SerialPort不能正常工作(它是一个USB虚拟COM端口),有时会吃掉几个字符,可能是一些windows驱动程序错误) 但是,演示不起作用:-/ 在窗体上使用propertygrid读取对象的属性,然后发送从远程设备读取属性的请求时,会发生一些非常奇怪的事情:一次对SendCommand进行多个同时调用 我尝试使用lock{}块使调用顺序化,但它不起作用。即使使用锁,也会有多个呼叫进入受保护区域 你能告诉我我做错了什么吗 我的代码:C# c语言中的锁定错误#,c#,multithreading,locking,serial-port,C#,Multithreading,Locking,Serial Port,我正在尝试用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被使用时,有大量的代码在运行。也不仅仅是微妙的。它非常慢,它可以抛出异常,它不会