C# 两个 Windows:第一个用于 main UI,第二个将作为后台进程 (Serial.Read)(活动)

C# Two Windows: First one for main UI, second one will be the background process (Serial.Read) (Active)

我是 C# WPF 的新手,我在 运行 安装我的程序时遇到了问题。如果我的串行端口(上面的 xbee)无法在特定时间(Dispatcher)接收到所需的数据,我的程序就会冻结。

所以,我试图通过创建第二个 window 来解决它,它将主动等待数据以减轻主 UI window 的负载。我遇到的问题是我无法 运行 第二个 window 与我的主 window 同步。

有什么建议吗?

主要UI

public partial class Window5 : Window
{
    SerialPort Senport = new SerialPort("COM4", 9600, Parity.None, 8, StopBits.One);
    int flagger=0;
    int fflagger=0;
    DispatcherTimer timer1 = new DispatcherTimer(); //Feed Timer
    DispatcherTimer timer2 = new DispatcherTimer();//Disabler Timer during Feeding
    DispatcherTimer timer3 = new DispatcherTimer();//Disabler Timer during Drinking
    DispatcherTimer timer4 = new DispatcherTimer();//Disabler Timer during Cleaning
    DispatcherTimer timer5 = new DispatcherTimer();// Serial Port Data Receiver Timer
    DispatcherTimer timer6 = new DispatcherTimer();//
    DispatcherTimer timer7 = new DispatcherTimer();
    public Window5()
    {   
        InitializeComponent();
        new Window2();
        timer1.Interval = TimeSpan.FromSeconds(300);
        timer2.Interval = TimeSpan.FromSeconds(15);
        timer3.Interval = TimeSpan.FromSeconds(15);
        timer4.Interval = TimeSpan.FromSeconds(60);
    }
    void timer_Tick(object sender, EventArgs e)
    {
        FEED.IsEnabled=true;
        timer1.Stop();
    }

    void FEED_Click(object sender, RoutedEventArgs e)
    {
        fflagger=1;
        flagger=1;
        Sender();
        timer1.Start();
        timer2.Start();
        Disabler();
        MessageBox.Show("Feeds Dispensing is starting","Drinking Water Process",MessageBoxButton.OK,MessageBoxImage.Information);
        timer1.Tick +=timer_Tick;
        timer2.Tick +=Enabler;
        // Xbee Code Will be Here
    }
    void FEED2_Click(object sender, RoutedEventArgs e)
    {
        flagger=2;
        Sender();
        timer3.Start();
        Disabler();
        MessageBox.Show("Water Dispensing is starting", "Drinking Water Process",MessageBoxButton.OK,MessageBoxImage.Information);
        timer3.Tick +=Enabler;
        // Xbee Code Will be Here
    }
    void Clean_Click(object sender, RoutedEventArgs e)
    {
        //Window4 w4 = new Window4();
        flagger=3;
        Sender();
        timer4.Start();
        Disabler();
        MessageBox.Show("Cleaning Process is starting", "Cleaning Process",MessageBoxButton.OK,MessageBoxImage.Information);
        //w4.Show();
        timer4.Tick +=Enabler;
    }

    void CCTV_Click(object sender, RoutedEventArgs e)
    {
        Process.Start(@"C:\Program Files\ CMS 2.0\CMS");
    }


    public void Enabler(object sender, EventArgs e)
    {       
        if(flagger==1)
        {
            FEED2.IsEnabled=true;
            Clean.IsEnabled=true;
            timer2.Stop();
        }
        else if(flagger==2 && fflagger==0)
        {
            FEED.IsEnabled=true;
            FEED2.IsEnabled=true;
            Clean.IsEnabled=true;
            timer3.Stop();
        }
        else if(flagger==2 && fflagger==1)
        {
            FEED2.IsEnabled=true;
            Clean.IsEnabled=true;
            timer3.Stop();
        }
        else if(flagger==3 && fflagger==0)
        {
            FEED.IsEnabled=true;
            FEED2.IsEnabled=true;
            Clean.IsEnabled=true;
            timer4.Stop();
        }
        else
        {
            FEED2.IsEnabled=true;
            Clean.IsEnabled=true;
            timer4.Stop();
        }
    }

    // Function in disabling Buttons
    public void Disabler()
    {
        if(flagger == 1)
        {
            FEED.IsEnabled=false;
            FEED2.IsEnabled=false;
            Clean.IsEnabled=false;  
        }
        else if(flagger == 2)
        {
            Clean.IsEnabled=false;
            FEED.IsEnabled=false;
            FEED2.IsEnabled=false;
        }
        else
        {
            Clean.IsEnabled=false;
            FEED.IsEnabled=false;
            FEED2.IsEnabled=false;
        }           
    }

    //Function for Serial Port Sender
    public void Sender()
    {
        if(flagger == 1)
        {
            try
            {
                if (!(Senport.IsOpen == true)) Senport.Open();
                Senport.Write("AB");
            }
            catch {}
        }

        else if(flagger == 2)
        {
            try
            {
                if (!(Senport.IsOpen == true)) Senport.Open();
                Senport.Write("BC");
            }
            catch {}
        }
        else if(flagger == 3)
        {
            try
            {
                if (!(Senport.IsOpen == true)) Senport.Open();
                Senport.Write("CD");
            }
            catch {}
        }       
        Senport.Close();    
    }
}

Window 2

 public partial class Window2 : Window
 {
     SerialPort Senport = new SerialPort("COM4", 9600, Parity.None, 8,StopBits.One);
     DispatcherTimer timer1 = new DispatcherTimer(); //Feed Timer
     DispatcherTimer timer2 = new DispatcherTimer();//Disabler Timer during Feeding
     DispatcherTimer timer3 = new DispatcherTimer();//Disabler Timer during Drinking
     DispatcherTimer timer4 = new DispatcherTimer();//Disabler Timer during Cleaning
     DispatcherTimer timer5 = new DispatcherTimer();// Serial Port Data Receiver Timer
     DispatcherTimer timer6 = new DispatcherTimer();//
     DispatcherTimer timer7 = new DispatcherTimer();
     string rdata;
     string rdata1;
     public Window2()
     {
         InitializeComponent();
         Window5 WORK1 = new Window5();
         while(true)
         {

             if (!(Senport.IsOpen == true)) Senport.Open();
             rdata= Senport.ReadLine();
             rdata1.ToString();
             rdata1 = rdata;
             Senport.Close();

             if(rdata1 == "FEED")
             {
                 MessageBox.Show("Feeds already being dispense!", "Feeding Process",MessageBoxButton.OK,MessageBoxImage.Information);
             }

             if(rdata1 == "DRINK")
             {
                 MessageBox.Show("Drinkable water is dispense!", "Drinking Water Process",MessageBoxButton.OK,MessageBoxImage.Information);
             }  

             if(rdata1 == "CLEAN")
             {
                 MessageBox.Show("Cleaning the cage is done!", "Cleaning Process",MessageBoxButton.OK,MessageBoxImage.Information);
             }              
         }
     }
}

Event based programming is largely superior to timer-based and blocking code. There are many levels of event based programming, from synchronous to asynchronous to full-fledged TPL. In your case, I would recommend starting by thoroughly reading the SerialPort documentation, and in particular the DataReceived 事件。

添加第二个 UI 会产生大量不必要的复杂性。

这是一个基本示例,说明如何将 SerialPorts 与状态机一起使用:

public class SerialPortTests
{
    //Your states are pretty obscure to me, I'm making those up.
    private enum States
    {
        State1,
        State2,
        State3
    }

    private States _state = States.State1;
    private SerialPort _port = new SerialPort(/*Enter your port's config here*/);

    public SerialPortTests()
    {
        _port.DataReceived += dataReceived; //This is the important line
        _port.Open();
    }

    private void dataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        var sendingPort = (SerialPort)sender;
        var data = sendingPort.ReadExisting(); //Careful, you may have more than 1 line in data.
        var dataLines = data.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
        foreach (var line in dataLines)
        {
            verifyState(line);
            processLine(line);
        }
    }

    private void verifyState(string line)
    {
        //Your states are pretty obscure, I'm just making things up here.
        if (line == "FEED" && _state != States.State1)
        {
            //Handle the error if you can, or just throw to learn more about the problem in the stack trace.
            throw new ApplicationException("received FEED while in state " + _state);
        }
    }

    private void processLine(string line)
    {
        if (line == "FEED")
        {
            //Don't use MessageBox unless you really have to. Change a label's text or something.
            Console.WriteLine("Feeds already being dispense!");
            _state = States.State2;
        }
    }  
}

本质上,SerialPort class 能够在知道有更多数据要读取时引发事件。您可以订阅该事件。在 C# 中,这称为 "handling" 并在这一行完成:

_port.DataReceived += dataReceived; //This is the important line

这一行表示"everytime the port raises the DataReceived event, execute the private void dataReceived(...) function that I declared further down".

您的问题是您在 while(true) 循环中使用了 SerialPort.ReadLine()。这几乎总是一个坏主意。 SerialPort.ReadLine() 是一个阻塞调用,正如文档告诉您的那样。您的代码将停止,直到它从 COM 端口读取换行符。如果这些字符不出现,您的程序将永远冻结。通过使用 "DataReceived" 事件,您可以保证有 数据要读取,所以即使我仍然调用 ReadExisting (这也是一个阻塞调用)我已经知道那里将是数据并且该行将执行并且 return 非常快。在大多数情况下,它会如此之快,以至于您的 UI 不会冻结足够长的时间让任何人注意到。

这是最低级别的基于事件的编程。如果您仍然看到冻结,则必须使用 multi-threading,这非常复杂。仅在绝对必要时才使用它。

其余代码与 SerialPorts 无关。它在那里是因为您使用定时器来模拟状态机,这也是一个坏主意。 DataReceived 将在接收到任何数据时触发,无论数据是什么,因此跟踪您的状态以将其与传入数据进行比较非常重要。如果您期望 "FEED"(即您的程序在 State1 或 StateFeed 中)并且您收到 "DRINK",则说明出现了问题,您必须对此采取措施。当出现问题时,您至少可以做的就是抛出异常。一旦您了解出了什么问题,您就可以开始添加优雅地处理异常的代码。