C#两个窗口:第一个用于主UI,第二个将是后台进程(Serial.Read)(活动)



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

因此,我试图通过制作第二个窗口来解决这个问题,该窗口将积极等待数据,以减轻主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.0CMS");
}

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();    
}
}

窗口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);
}              
}
}
}

基于事件的编程在很大程度上优于基于定时器和阻塞代码。基于事件的编程有很多级别,从同步到异步再到全面的TPL。在您的情况下,我建议您首先彻底阅读SerialPort文档,尤其是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类能够在知道有更多数据要读取时引发事件。您可以订阅该活动。在C#中,这被称为"处理",它在以下行中完成:

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

这一行的意思是"每次端口引发DataReceived事件时,执行我进一步声明的私有void DataReceived(…)函数"。

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

这是最低级别的基于事件的编程。如果你仍然看到冻结,你必须使用多线程,这是非常复杂的。只有在绝对必要的情况下才能使用。

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

最新更新