C#等待serialPort_DataReceived事件的反馈

C#等待serialPort_DataReceived事件的反馈,c#,winforms,user-interface,serial-port,timeout,C#,Winforms,User Interface,Serial Port,Timeout,我正在使用我正在开发的WinFormGUI与串行端口上的微控制器通信 我根据预定义的协议发送一组命令,并从微控制器接收反馈字符串 我想知道是否有一种简单的方法可以在发送命令后等待某个反馈 例如 发出命令 等待设定的时间(可能是几秒钟到几分钟) 及时显示反馈,并继续发出下一个命令/行动 如果未及时收到反馈,将触发超时并显示故障消息。如果数据及时返回,应立即停止等待方法,并继续执行下一个操作过程。我不想在等待反馈时阻塞UI 我使用以下代码来接收数据 delegate void SetText

我正在使用我正在开发的WinFormGUI与串行端口上的微控制器通信

我根据预定义的协议发送一组命令,并从微控制器接收反馈字符串

我想知道是否有一种简单的方法可以在发送命令后等待某个反馈

例如

  • 发出命令
  • 等待设定的时间(可能是几秒钟到几分钟)
  • 及时显示反馈,并继续发出下一个命令/行动
  • 如果未及时收到反馈,将触发超时并显示故障消息。如果数据及时返回,应立即停止等待方法,并继续执行下一个操作过程。我不想在等待反馈时阻塞UI

    我使用以下代码来接收数据

        delegate void SetTextCallback(string text);
    
        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                string receivedData = serialPort1.ReadExisting();
                SetText(receivedData);
            }
            catch (IOException exception)
            {
                MessageBox.Show(exception.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (Exception exception)
            {
                MessageBox.Show(exception.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    
        private void SetText(string text)
        {
            if (this.textBoxReceive.InvokeRequired)
            {
                SetTextCallback d = SetText;
                this.Invoke(d, new object[] { text });
                //this.Invoke(new Action(() => { this.textBoxReceive.AppendText(text); }));
    
            }
            else
            {
                if (text.Length > 15)
                {
                    CheckPosition(text); //To check for a position number from the feedback string
                }
                this.textBoxReceive.AppendText(text);
    
            }
        }
    
    这是我的写作方法

        private void SendCommand(int move, int trigger)
        {
            try
            {
                if (serialPort1.IsOpen)
                {
                    string cmd = string.Empty;
                    //Format: { “move”: 0, “trigger”: 0}
                    cmd = "{ " + "\"move\": " + move + ","
                          + " \"trigger\": " + trigger 
                          + " }"
                          + Environment.NewLine;
                    textBoxReceive.AppendText("Send:" + cmd + Environment.NewLine); // Display sent command
                    serialPort1.DiscardOutBuffer();
                    serialPort1.DiscardInBuffer();
                    serialPort1.Write(cmd);
    
                }
                else if (serialPort1.IsOpen != true)
                {
                    MessageBox.Show(@"Lost COM Port.", "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
    
            }
            catch (IOException e)
            {
                MessageBox.Show(e.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
    
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
    
            }
    
        }
    
    我有一个点击按钮的方法,我一直在和延迟(///开始等待)斗争,就像这样

    我对线程和手动重置事件等不太熟悉

    请帮助了解等待数据的最佳方式,最好是使用代码示例


    非常感谢。

    这里有一个简单的解决方案:

    这样做的目的是在开始发送命令时创建一个单独的线程, 这是通过以下方式实现的:

                Task.Factory.StartNew(() => {
            }, _canecellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
    
    这里的参数主要是为自己说话:

  • 是我希望在此线程/任务上执行的函数
  • 取消令牌,这样我就可以在不再需要发送命令时终止线程
  • “LongRunning”选项表示这将是一个长时间运行的任务。你可以阅读更多关于它的内容。
  • 传递默认计划程序
  • 接下来需要做的是创建AutoResteEvent的实例。它是如何工作的,你可以详细阅读。但简而言之,它是一个开关,有两种状态,打开和关闭。默认情况下,您希望它被关闭,这就是构造函数中的false参数的作用。 在串行端口(DataReceived)的事件处理程序中,您要“打开”AutoResetEvent。所以你要这样做:

    dataReceivedEvent.Set(); 
    
    现在,当您发出命令时,您将等待AutoResetEvent被“打开”,并指定您愿意等待的时间,如下所示:

    var succeeded = dataReceivedEvent.WaitOne(TimeSpan.FromSeconds(3));
    
    这意味着,如果在3秒内AutoResetEvent未打开,请停止等待并报告故障。基本上,如果在给定的时间范围内没有打开它,则返回false;如果在给定的时间范围内打开,则返回true。由于它是“自动”重置事件,在完成等待后,它将自动“关闭”自身,因此您不必手动重置

    剩下的都是你已经拥有的。使用invoke与UI交互并读取/发送命令

     public class Communicator
    {
        CancellationTokenSource _canecellationTokenSource = new CancellationTokenSource();
        List<Command> _scenario = new List<Command>(6)
        {
            Command.Trigger(1),
            Command.MoveTo(2),
            Command.Trigger(2),
            Command.MoveTo(3),
            Command.Trigger(3),
            Command.MoveTo(1)
        };
    
        public void Start(ListBox feedbackControl)
        {
            Task.Factory.StartNew(() => {
                var dataReceivedEvent = new AutoResetEvent(false);
                var ct = _canecellationTokenSource.Token;
                var controller = new DummyMicrocontroller();
                DataReceivedEventHandler onDataReceived = (cmd) => { dataReceivedEvent.Set(); };
                controller.DataReceived += onDataReceived;
                foreach (var cmd in _scenario)
                {
                    if (ct.IsCancellationRequested)
                    {
                        AddItemSafe(feedbackControl, $"Operation cancelled...");
                        break;
                    }
    
                    AddItemSafe(feedbackControl, cmd.GetMessage(Command.MessageType.Info));
                    controller.Send(cmd);
                    var succeeded = dataReceivedEvent.WaitOne(TimeSpan.FromSeconds(3));
                    var messageType = succeeded ? Command.MessageType.Success : Command.MessageType.Error;
                    AddItemSafe(feedbackControl, cmd.GetMessage(messageType));
                }
    
                AddItemSafe(feedbackControl, $"Finished executing scenario.");
    
                controller.DataReceived -= onDataReceived;
    
            }, _canecellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
    
        }
    
        public void Stop(ListBox feedbackControl)
        {
            AddItemSafe(feedbackControl, $"Attempting to cancel...");
            _canecellationTokenSource.Cancel();
        }
    
        private void AddItemSafe(ListBox feedbackControl, object item)
        {
            if (feedbackControl.InvokeRequired)
            {
                feedbackControl.Invoke((MethodInvoker)delegate { AddItemSafe(feedbackControl, item); });
            }
            else
            {
                feedbackControl.Items.Add(item);
            }
        }
    }
    
    公共类通信器
    {
    CancellationTokenSource _canecellationTokenSource=新的CancellationTokenSource();
    列表_场景=新列表(6)
    {
    命令触发器(1),
    命令。移动到(2),
    命令触发器(2),
    命令。移动到(3),
    指令触发器(3),
    命令。移动到(1)
    };
    公共无效启动(列表框反馈控制)
    {
    Task.Factory.StartNew(()=>{
    var dataReceivedEvent=新的自动存储事件(假);
    var ct=\u canecellationTokenSource.Token;
    var控制器=新的Dummy微控制器();
    DataReceivedEventHandler onDataReceived=(cmd)=>{dataReceivedEvent.Set();};
    controller.DataReceived+=onDataReceived;
    foreach(在_场景中为var cmd)
    {
    如果(ct.IsCancellationRequested)
    {
    AddItemSafe(反馈控制,$“操作已取消…”);
    打破
    }
    AddItemSafe(feedbackControl,cmd.GetMessage(Command.MessageType.Info));
    控制器发送(cmd);
    var succeed=dataReceivedEvent.WaitOne(TimeSpan.FromSeconds(3));
    var messageType=successed?Command.messageType.Success:Command.messageType.Error;
    AddItemSafe(feedbackControl,cmd.GetMessage(messageType));
    }
    AddItemSafe(反馈控制,$“已完成执行场景”);
    controller.DataReceived-=onDataReceived;
    },_canecellationTokenSource.Token、TaskCreationOptions.LongRunning、TaskScheduler.Default);
    }
    公共无效停止(列表框反馈控制)
    {
    AddItemSafe(反馈控制,$“尝试取消…”);
    _canecellationTokenSource.Cancel();
    }
    私有void AddItemSafe(列表框反馈控件,对象项)
    {
    if(feedbackControl.invokererequired)
    {
    调用((MethodInvoker)委托{AddItemSafe(feedbackControl,item);});
    }
    其他的
    {
    反馈控制.项目.添加(项目);
    }
    }
    }
    
    UI保留在自己的线程上,不受影响。
    由于我没有可用的微控制器,我不得不编写一个虚拟模拟器:)

    非常感谢您的帮助,狂暴者网络!由于我不熟悉所有这些多线程术语/概念,请您将其简化为新手级别,说明哪个方法在做什么,哪个方法调用什么,以及如何修改我的方法。我已经更新了我的答案,使其更具解释性,希望能有所帮助。更新代码以使用我提供的解决方案对您来说应该不难。用真正的串行端口驱动控制器替换DimMyCube,并考虑在手之前有一个命令数组,这样就不必重复代码并发出另一个命令。谢谢你对我有耐心。我从你身上学到了很多。这很有帮助。另一个问题:这里的超时时间固定为3秒。如果我需要发送一个命令,并且PCB/uC在完成任何操作后半小时向我报告,该怎么办?因为每个命令都会导致uC执行不同的操作,因此会有所不同
     public class Communicator
    {
        CancellationTokenSource _canecellationTokenSource = new CancellationTokenSource();
        List<Command> _scenario = new List<Command>(6)
        {
            Command.Trigger(1),
            Command.MoveTo(2),
            Command.Trigger(2),
            Command.MoveTo(3),
            Command.Trigger(3),
            Command.MoveTo(1)
        };
    
        public void Start(ListBox feedbackControl)
        {
            Task.Factory.StartNew(() => {
                var dataReceivedEvent = new AutoResetEvent(false);
                var ct = _canecellationTokenSource.Token;
                var controller = new DummyMicrocontroller();
                DataReceivedEventHandler onDataReceived = (cmd) => { dataReceivedEvent.Set(); };
                controller.DataReceived += onDataReceived;
                foreach (var cmd in _scenario)
                {
                    if (ct.IsCancellationRequested)
                    {
                        AddItemSafe(feedbackControl, $"Operation cancelled...");
                        break;
                    }
    
                    AddItemSafe(feedbackControl, cmd.GetMessage(Command.MessageType.Info));
                    controller.Send(cmd);
                    var succeeded = dataReceivedEvent.WaitOne(TimeSpan.FromSeconds(3));
                    var messageType = succeeded ? Command.MessageType.Success : Command.MessageType.Error;
                    AddItemSafe(feedbackControl, cmd.GetMessage(messageType));
                }
    
                AddItemSafe(feedbackControl, $"Finished executing scenario.");
    
                controller.DataReceived -= onDataReceived;
    
            }, _canecellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
    
        }
    
        public void Stop(ListBox feedbackControl)
        {
            AddItemSafe(feedbackControl, $"Attempting to cancel...");
            _canecellationTokenSource.Cancel();
        }
    
        private void AddItemSafe(ListBox feedbackControl, object item)
        {
            if (feedbackControl.InvokeRequired)
            {
                feedbackControl.Invoke((MethodInvoker)delegate { AddItemSafe(feedbackControl, item); });
            }
            else
            {
                feedbackControl.Items.Add(item);
            }
        }
    }