我的程序是这样的:
我按下一个打开端口的单选按钮
接下来我按下按钮"阅读"它启动一个线程,使用port.ReadLine()
从串行端口连续读取数据并将其打印在文本框中;
我有另一个收音机应首先加入线程,然后关闭端口;问题是打印顺利,直到我关闭端口时UI冻结。
public Form1()
{
mythread = new Thread(ReadFct);
myPort = new SerialPort("COM3", 9600);
myPort.ReadTimeout = 3500;
InitializeComponent();
foreach (var t in Constants.ComboParameters)
this.paramCombo.Items.Add(t);
radioClose.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
radioOpen.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
}
以下是附加到帖子的功能
void ReadFct()
{
string aux = "";
while (readCondition)
{
if (myPort.IsOpen)
aux = myPort.ReadLine();
this.SetText(aux);
}
}
下面是单选按钮事件处理程序
public void radioButtonCheckedChanged(object sender,EventArgs e)
{
if (radioOpen.Checked && !myPort.IsOpen)
try
{
myPort.Open();
mythread.Start();
}
catch (Exception)
{
MessageBox.Show("Nu s-a putut deschide port-ul");
}
if (radioClose.Checked && myPort.IsOpen)
{
readCondition = false;
mythread.Join();
myPort.Close();
// myPort.DataReceived -= DataReceivedHandler;
}
}
读取按钮功能:
private void readbtn_Click(object sender, EventArgs e)
{
if (!myPort.IsOpen)
MessageBox.Show("PORT NOT OPENED!");
else
{
// myPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
readCondition = true;
if (!mythread.IsAlive)
{
mythread = new Thread(ReadFct);
mythread.Start();
}
}
我使用了MSDN从另一个线程更改控件时建议的内容:
private void SetText(string text)
{
if (this.textBox1.InvokeRequired)
{
StringTb del = new StringTb(SetText);
this.Invoke(del, new object[] { text });
}
else
SetData = text;
}
答案 0 :(得分:2)
很难确切地知道你需要什么,缺乏一个好的Minimal, Complete, and Verifiable code example来说明问题。也就是说,这里的问题是Thread.Join()
方法导致该线程停止执行任何其他工作,并且用于调用该方法的线程是处理所有用户界面的线程。更糟糕的是,如果您的端口永远不会收到另一个换行符,那么您等待的主题将永远不会终止,因为您等待ReadLine()
方法。更糟糕的是,即使你确实得到换行符,如果你在等待Thread.Join()
时发生了这种情况,那么对Invoke()
的调用将会死锁,因为它需要UI线程才能执行工作,Thread.Join()
调用阻止它获取UI线程。
换句话说,您的代码存在多个问题,其中任何一个问题都可能导致问题,但所有问题都意味着它可能无法正常工作。
有很多策略可以解决这个问题,但恕我直言,最好是使用await
。这样做的第一步是改变您的I / O处理,使其异步完成,而不是专门给它一个线程:
// Ideally, you should rename this method to "ReadFctAsync". I am leaving
// all names intact for the same of the example though.
async Task ReadFct()
{
string aux = "";
using (StreamReader reader = new StreamReader(myPort.BaseStream))
{
while (true)
{
aux = await reader.ReadLineAsync();
// This will automatically work, because the "await" will automatically
// resume the method execution in the UI thread where you need it.
this.SetText(aux);
}
}
}
然后,不是显式创建一个线程,而是通过调用上面的方法创建一个Task
对象:
public Form1()
{
// In this approach, you can get rid of the "mythread" field altogether
myPort = new SerialPort("COM3", 9600);
myPort.ReadTimeout = 3500;
InitializeComponent();
foreach (var t in Constants.ComboParameters)
this.paramCombo.Items.Add(t);
radioClose.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
radioOpen.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
}
public async void radioButtonCheckedChanged(object sender,EventArgs e)
{
if (radioOpen.Checked && !myPort.IsOpen)
{
try
{
myPort.Open();
await ReadFct();
// Execution of this method will resume after the ReadFct() task
// has completed. Which it will do only on throwing an exception.
// This code doesn't have any continuation after the "await", except
// to handle that exception.
}
catch (Exception)
{
// This block will catch the exception thrown when the port is
// closed. NOTE: you should not catch "Exception". Figure out what
// *specific* exceptions you expect to happen and which you can
// handle gracefully. Any other exception can mean big trouble,
// and doing anything other than logging and terminating the process
// can lead to data corruption or other undesirable behavior from
// the program.
MessageBox.Show("Nu s-a putut deschide port-ul");
}
// Return here. We don't want the rest of the code executing after the
// continuation, because the radio button state might have changed
// by then, and we really only want this call to do work for the button
// that was selected when the method was first called. Note that it
// is probably even better if you just break this into two different
// event handlers, one for each button that might be checked.
return;
}
if (radioClose.Checked && myPort.IsOpen)
{
// Closing the port should cause `ReadLineAsync()` to throw an
// exception, which will terminate the read loop and the ReadFct()
// task
myPort.Close();
}
}
在上面,我完全忽略了readbtn_Click()
方法。由于缺乏一个好的MCVE,它不清楚该按钮在整个方案中扮演的角色。您似乎有一个单选按钮组(两个按钮),用于控制端口是打开还是关闭。目前尚不清楚为什么你有一个额外的常规按钮,似乎也可以打开端口并开始阅读,独立于无线电组。
如果您想要这个额外的按钮,在我看来它应该做的就是通过检查"打开"来改变无线电组状态。单选按钮。然后让无线电组按钮处理端口状态和读取。如果您需要有关如何将上面的代码示例与整个UI完全集成的更具体建议,则需要提供更多详细信息,最好是在新问题中。这个新问题必须包括一个好的MCVE。