Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/16.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在C中在不同线程上等待事件的正确方法#_C#_.net_Multithreading_.net Core - Fatal编程技术网

C# 在C中在不同线程上等待事件的正确方法#

C# 在C中在不同线程上等待事件的正确方法#,c#,.net,multithreading,.net-core,C#,.net,Multithreading,.net Core,上下文:我正在编写一个.NET库,它通过串行端口与POS设备通信。我必须通过串行端口发送特定命令,然后等待设备回复的最长时间 据我所知,.NET中的SerialPort对象使用事件处理程序在通过线路返回字节时发出信号,并且该事件处理程序在单独的线程上执行 我的计划是使用一个主线程来管理与设备的通信,等待接收字节、解析、响应等,并让SerialPort.DataReceived的事件处理程序以某种方式向管理通信的线程发出“信号”,而该线程必须等待来自设备的字节。如果用户决定取消操作,主线程上也可能

上下文:我正在编写一个.NET库,它通过串行端口与POS设备通信。我必须通过串行端口发送特定命令,然后等待设备回复的最长时间

据我所知,.NET中的SerialPort对象使用事件处理程序在通过线路返回字节时发出信号,并且该事件处理程序在单独的线程上执行

我的计划是使用一个主线程来管理与设备的通信,等待接收字节、解析、响应等,并让
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,但任何写/读方法都可以使用相同的模式

    如果代码有效,那么询问它是“正确的方式”还是“最佳方式”或类似的问题主要是基于意见的。也就是说,对e