2009-09-04 81 views
5

可能重复:
Getting Cross-thread operation not valid
Cross-thread operation not valid跨线程操作无效

我试图让我创造新的处理程序以侦听COM端口用于SerialPort.DataReceived事件。逻辑很简单 - 我向TextBox1写东西,按Button1,我的文本应该在Label1中显示它自己。但我的应用程序不想运行,因为它会抛出'跨线程操作无效'错误。 我做了一些搜索并找到了Invoke对象 - 我如何在我的例子中使用它?为什么我需要包含调用逻辑?

namespace WindowsApplication1 
{ 
public partial class Form1 : Form 
{ 
    SerialPort sp = new SerialPort(); 

    public Form1() 
    { 
     InitializeComponent(); 
     sp.DataReceived += MyDataReceivedHandler; 
    } 

    private void Form1_Load(object sender, EventArgs e) 
    { 

    } 

    private void MyDataReceivedHandler(object sender, SerialDataReceivedEventArgs e) 
    { 
     try 
     { 
      //sp.PortName = "COM3"; 
      //sp.Open(); 
      Label1.Text = sp.ReadLine(); 
     } 
     catch (Exception exception) 
     { 
      RichTextBox1.Text = exception.Message + "\n\n" + exception.Data; 
     } 
     finally 
     { 
      sp.Close(); 
     } 
    } 

    private void button1_Click(object sender, EventArgs e) 
    { 
     try 
     { 
      sp.PortName = "COM3"; 
      sp.Open(); 
      sp.WriteLine(TextBox1.Text); 
     } 
     catch (Exception exception) 
     { 
      RichTextBox1.Text = exception.Message + "\n\n" + exception.Data; 
     } 
     finally 
     { 
      sp.Close(); 
     } 
    } 
} 

}

+0

@ Peter:这里的串口是RS232串口。尽管USB和所有仍然有一些设备(例如GPS,医疗)使用串行端口进行PC通信。 – Sesh 2009-09-04 06:55:49

+0

@_simon_:只是好奇:在这个特定的应用程序中使用的COM端口是什么? – 2009-09-04 07:31:14

+0

@_simon_:我更新了我的答案 – 2009-09-04 09:13:57

回答

17

我的猜测是,MyDataReceivedHandler是在不同的线程不是GUI运行。为了解决这个问题,你需要在正确的线程上调用Text设置器。这是这样的一个样本:

public void SetControlText(Control control, string text) 
{ 
    if (this.InvokeRequired) 
    { 
     this.Invoke(new Action<Control,string>(SetControlText), new object[] { control, text }); 
    } 
    else 
    { 
     control.Text = text; 
    } 
} 

private void MyDataReceivedHandler(object sender, SerialDataReceivedEventArgs e) 
{ 
    try 
    { 
     //sp.PortName = "COM3"; 
     //sp.Open(); 
     SetControlText(Label1, sp.ReadLine()); 
    } 
    catch (Exception exception) 
    { 
     SetControlText(RichTextBox1, exception.Message + "\n\n" + exception.Data); 
    } 
    finally 
    { 
     sp.Close(); 
    } 
} 

如果您使用的是.NET Framework 2.0中,上述Action<T1, T2>委托是不可用的,所以你必须定义自己的一个:

private delegate void SetControlTextHandler(Control control, string text); 

public void SetControlText(Control control, string text) 
{ 
    if (this.InvokeRequired) 
    { 
     this.Invoke(new SetControlTextHandler(SetControlText), new object[] { control, text }); 
    } 
    else 
    { 
     control.Text = text; 
    } 
} 

SetControlText方法可以这样做短(甚至静态)(此工作在两个2.0和3.5):

public static void SetControlText(Control control, string text) 
{ 
    ´control.Invoke((MethodInvoker)delegate { control.Text = text; }); 
} 

那么你不需要做检查,但是您将在另一方面将呼叫包装在委托中,即使它不是必需的。我认为在像这样的GUI方法中,这两者之间的任何性能差异都是可以忽略的,所以我倾向于使用较短的形式,因为它的代码编写较少。

+0

看起来,这只适用于3.5。我使用Visual Studio 2005,现在我安装了3.5 SP1。在Visual Studio 2005中,我可以设置使用哪个.NET框架? – sventevit 2009-09-04 08:07:08

+0

@_simon_:我用2.0兼容版本更新了答案 – 2009-09-04 09:07:22

+0

注意:如果在委托中执行的操作长时间运行,它仍然可以阻止UI,因为调用只会导致UI线程处理操作。在实现它的所有控件上使用BeginInvoke将以异步方式执行操作,而不会阻塞。 – 2009-09-04 20:38:11

0

您也可以执行以下操作每当从一个不同的线程访问UI控制比它被上创建的一个:

(.NET 3.5)

myControl.BeginInvoke(new MethodInvoker(() => myControl.whatever = whatever;)); 

或 (.NET 2.0)

myControl.BeginInvoke(new MethodInvoker(delegate { myControl.whatever = whatever;)); 

编辑>有时候使用调用长期运行的操作可/仍将挂UI,使用明显的BeginInvoke执行该操作异步的d ui不会挂起。