C# 在windows窗体应用程序中加入工作线程
我的程序是这样工作的: 我按下一个单选按钮打开端口。C# 在windows窗体应用程序中加入工作线程,c#,multithreading,forms,C#,Multithreading,Forms,我的程序是这样工作的: 我按下一个单选按钮打开端口。 接下来,我按下一个按钮“Read”,它启动一个线程,该线程使用Port.ReadLine()从串行端口连续读取数据,并将其打印到文本框中 我有另一台收音机,它应该先加入线程,然后关闭端口;问题是打印工作进展顺利,直到UI冻结时关闭端口 public Form1() { mythread = new Thread(ReadFct); myPort = new SerialPort(
接下来,我按下一个按钮“Read”,它启动一个线程,该线程使用
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;
}
很难确切地知道你需要什么,因为缺乏一个好的例子来说明这个问题。也就是说,这里的问题是
Thread.Join()
方法会导致该线程停止执行任何其他工作,而用来调用该方法的线程就是处理所有用户界面的线程。更糟糕的是,如果您的端口从未收到另一个换行符,那么您等待的线程将永远不会终止,因为您一直在等待ReadLine()
方法。更糟糕的是,即使您确实获得了一个换行符,如果在等待线程.Join()
时发生这种情况,对Invoke()
的调用将死锁,因为它需要UI线程来完成其工作,Thread.Join()
调用阻止它获得UI线程
换句话说,您的代码有多个问题,其中任何一个都可能导致问题,但所有这些加在一起意味着它不可能工作
有各种各样的策略来解决这个问题,但最好的方法是使用wait
。执行此操作的第一步是更改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);
}
}
}
然后,不显式创建线程,只需通过调用上面的
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\u Click()
方法。由于缺乏良好的MCVE,不清楚按钮在整个方案中扮演什么角色。您似乎有一个单选按钮组(由两个按钮组成),用于控制端口是打开还是关闭。现在还不清楚为什么会有一个额外的常规按钮,它似乎也能够打开端口并开始阅读,而不依赖于收音机组
如果你想要那个额外的按钮,在我看来,它应该做的就是通过选中“打开”单选按钮来改变无线组的状态。然后让单选组按钮处理端口状态和读数。如果您需要关于如何将上面的代码示例与整个UI完全集成的更具体的建议,您将需要提供更多的细节,最好是在一个新问题中。这个新问题必须包括一个好的MCVE。很难确切知道你需要什么,因为缺少一个好的例子来说明这个问题。也就是说,这里的问题是
Thread.Join()
方法会导致该线程停止执行任何其他工作,而用来调用该方法的线程就是处理所有用户界面的线程。更糟糕的是,如果您的端口从未收到另一个换行符,那么您等待的线程将永远不会终止,因为您一直在等待ReadLine()
方法。更糟糕的是,即使您确实获得了一个换行符,如果在等待线程.Join()
时发生这种情况,对Invoke()
的调用将死锁,因为它需要UI线程来完成其工作,Thread.Join()
调用阻止它获得UI线程
换句话说,您的代码有多个问题,其中任何一个都可能导致问题,但所有这些加在一起意味着它不可能工作
有各种各样的策略来解决这个问题,但最好的方法是使用wait
。执行此操作的第一步是更改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);
}
}
}
然后,不显式创建线程,只需通过调用上面的
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\u Click()
方法。由于缺乏良好的MCVE,不清楚按钮在整个方案中扮演什么角色。您似乎有一个单选按钮组(由两个按钮组成),用于控制端口是打开还是关闭。现在还不清楚为什么会有一个额外的常规按钮,它似乎也能够打开端口并开始阅读,而不依赖于收音机组
如果你想要那个额外的按钮,在我看来,它应该做的就是通过选中“打开”单选按钮来改变无线组的状态。然后让单选组按钮处理端口状态和读数。如果您需要关于如何将上面的代码示例与整个UI完全集成的更具体的建议,您将需要提供更多的细节,最好是在一个新问题中。这个新问题必须包含一个好的MCVE。而
SetText
方法是……每次您进行更改时,此代码似乎会生成2个radioButtonCheckedChanged事件。我认为你的代码可以处理这个,但是。。。每个单选按钮都会为true->false和false->true生成一个事件,因此单击一个按钮,两个按钮都会创建相同的事件。我会将radioButtonCheckedChanged函数分为两部分(因为它实际上是两个分开的函数),线程被困在ReadLine()调用中,因此它永远不会看到停止运行的请求。除非设备正在发送数据,否则这在某种程度上是不可预测的。关闭()串行端口,这将轰炸ReadLine()调用,捕获异常。或者选择DataReceived事件而不是使用线程。感谢您提醒我我忘记了。我请求使用SetText
方法,因为我怀疑您在内部使用了Invoke
。如果加入
工作线程并在中调用