C# 在C中在不同线程上等待事件的正确方法#
上下文:我正在编写一个.NET库,它通过串行端口与POS设备通信。我必须通过串行端口发送特定命令,然后等待设备回复的最长时间 据我所知,.NET中的SerialPort对象使用事件处理程序在通过线路返回字节时发出信号,并且该事件处理程序在单独的线程上执行 我的计划是使用一个主线程来管理与设备的通信,等待接收字节、解析、响应等,并让C# 在C中在不同线程上等待事件的正确方法#,c#,.net,multithreading,.net-core,C#,.net,Multithreading,.net Core,上下文:我正在编写一个.NET库,它通过串行端口与POS设备通信。我必须通过串行端口发送特定命令,然后等待设备回复的最长时间 据我所知,.NET中的SerialPort对象使用事件处理程序在通过线路返回字节时发出信号,并且该事件处理程序在单独的线程上执行 我的计划是使用一个主线程来管理与设备的通信,等待接收字节、解析、响应等,并让SerialPort.DataReceived的事件处理程序以某种方式向管理通信的线程发出“信号”,而该线程必须等待来自设备的字节。如果用户决定取消操作,主线程上也可能
SerialPort.DataReceived的事件处理程序以某种方式向管理通信的线程发出“信号”,而该线程必须等待来自设备的字节。如果用户决定取消操作,主线程上也可能涉及CancellationToken
因此,我需要:
要使一个线程(主通信逻辑线程)等待
…直到第二个线程(触发DataReceived
事件处理程序的线程)执行某些工作,或者直到出现超时
最初我尝试使用Monitor.Pulse
和Monitor.Wait
来实现这一点,但我意识到如果另一个线程不释放锁,那么Monitor.Wait
实际上永远不会返回,因此我的主线程可能会永远等待(比如通信失败等)
在那之后,我开始研究EventWaitHandle
,但我不确定这是否是正确的选择。我现在的计划如下:
创建一个新的EventWaitHandle(false,EventResetMode.AutoReset)
WaitHandle.WaitAny(newwaithandle[]{u eventWaitHandle},timeoutmillizes)代码>
\u eventWaitHandle.Set()代码>
EventWaitHandle
实例已经发信号,WaitAny将立即成功返回
我真的很想知道我在这里是否对我的计划做出了错误的假设,因为多线程编程确实不是我所熟悉的东西
EDIT:因为我意识到最初并不清楚,所以我真正想问的是,使用EventWaitHandle
和WaitHandle.WaitAny
是否正确,让一个线程等待第二个线程通知它(在指定的时间范围内)完成了一些工作,然后等待的线程可以继续它的工作
稍后编辑:特别是对于SerialPort
,我确实考虑过避免使用线程同步,并通过SerialPort.BaseStream
使用async/wait
,但似乎基本上需要通过提供的事件重新实现已经可用的功能,特别是因为ReadAsync
不会通过CancellationToken
在超时时真正取消,除非传递的令牌已经取消(在其他相关问题上发现了这一点,并验证了我自己),这对我来说有点违背了使用它的目的
我最终决定将我的“控制”线程包装在一些
async/await
代码中,然后通过AutoResetEvent
将其与SerialPort
创建的线程同步,因为只有两个线程需要同步,并且它会自动更改状态。感谢您指出它是可用的 您不必使用数据接收事件。串行端口确实公开了在这种情况下可能适合使用的常规同步读/写方法
为了使这样的同步资源更易于使用,最好将它们封装在异步接口中。例如:
public class MySerial
{
private SerialPort mySerialPort = new ();
private BlockingCollection<(TaskCompletionSource<string> result, string data)> queue = new(new ConcurrentQueue<(TaskCompletionSource<string> result, string data)>());
public MySerial() => Task.Run(ThreadMain);
private void ThreadMain()
{
// loop will block if no messages are queued
// call queue.CompleteAdding() to end the enumerable and exit the loop.
foreach (var (tcs, input) in queue.GetConsumingEnumerable())
{
mySerialPort.WriteLine(input);
var result = mySerialPort.ReadLine();
tcs.SetResult(result);
}
}
public Task<string> QueueWrite(string input)
{
var tcs = new TaskCompletionSource<string>();
queue.Add((tcs, input));
return tcs.Task;
}
}
公共类MySerial
{
private SerialPort mySerialPort=new();
private BlockingCollection queue=new(new ConcurrentQueue());
public MySerial()=>Task.Run(ThreadMain);
私有void ThreadMain()
{
//如果没有消息排队,循环将被阻止
//调用queue.CompleteAdding()以结束可枚举项并退出循环。
foreach(队列中的var(tcs,input)。getconsumineGenumerable()
{
mySerialPort.WriteLine(输入);
var result=mySerialPort.ReadLine();
tcs.SetResult(结果);
}
}
公共任务队列写入(字符串输入)
{
var tcs=new TaskCompletionSource();
添加((tcs,输入));
返回tcs.Task;
}
}
这使用一个线程来管理与串行端口的所有通信,并使用TaskCompletionSources来报告进度。请注意,这已大大简化,以保持简短,需要考虑以下事项:
- 您可能应该设置读/写超时
- 您可能希望在读取/写入串行端口时捕获异常,并将任何异常转发到taskCompletionSource
- 即使未处理任何消息,这也会阻止线程。在您的用例中,这可能是一个问题
- 可以添加取消支持
- 在使用事件时可以使用相同的模式,但会有点麻烦
- 这使用WriteLine/ReadLine,但任何写/读方法都可以使用相同的模式