需要有关事件处理和代表的解释

发布于 2024-10-19 07:07:31 字数 1237 浏览 6 评论 0原文

我写了一些代码,它可以实现我想要的功能。但是,我不太确定它到底是如何工作的。我最麻烦的部分是最后一部分。我有一个 textBox1.Text = "test" 不起作用。我收到一个关于从不同线程调用它的运行时错误。当我放置 textBox1.Invoke(等等)时,它按预期工作。为什么?

正如您所看到的,我所知道的信息足以构成危险,我真的很想了解这里发生的事情,而不是盲目地从网络上的网站复制和粘贴。

我在名为 SerialCommunicator 的类中具有以下内容:

public SerialCommunicator(SerialPort sp)
{
    this.sp = sp;
    sp.ReceivedBytesThreshold = packetSize;
    sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
    sp.Open();
}    
public void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Thread.Sleep(50);
    SerialPort s = (SerialPort)sender;
    byte[] buffer = new byte[128];
    s.Read(buffer, 0, s.BytesToRead);
}

然后,在我的 Form1.cs 中,我有一个按钮,按下该按钮时会执行以下操作:

private void btnPortOK_Click(object sender, EventArgs e)
{
    string comPort = cboComPorts.SelectedItem.ToString();
    SerialPort sp = new SerialPort(comPort, 9600, Parity.None, 8, StopBits.One);
    sp.DataReceived += new SerialDataReceivedEventHandler(DataHasBeenReceived);
    comm = new SerialCommunicator(sp);
}
public void DataHasBeenReceived(object sender, EventArgs args)
{
    textBox1.Invoke(new EventHandler(delegate { textBox1.Text += "test"; }));
}

I have some code that I wrote, which does what I want. However, I am not quite sure how, exactly, it works. The part I am having the most trouble with is the last part. I had a textBox1.Text = "test" which did not work. I got a run time error about it being called from a different thread. When I put the textBox1.Invoke(etc etc), it worked as expected. Why?

As you can see, I know just enough to be dangerous and I really want to understand what's going on here instead of blindly copying and pasting from sites around the web.

I have the following in a class named SerialCommunicator:

public SerialCommunicator(SerialPort sp)
{
    this.sp = sp;
    sp.ReceivedBytesThreshold = packetSize;
    sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
    sp.Open();
}    
public void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Thread.Sleep(50);
    SerialPort s = (SerialPort)sender;
    byte[] buffer = new byte[128];
    s.Read(buffer, 0, s.BytesToRead);
}

Then, in my Form1.cs I have a button that when pressed does the following:

private void btnPortOK_Click(object sender, EventArgs e)
{
    string comPort = cboComPorts.SelectedItem.ToString();
    SerialPort sp = new SerialPort(comPort, 9600, Parity.None, 8, StopBits.One);
    sp.DataReceived += new SerialDataReceivedEventHandler(DataHasBeenReceived);
    comm = new SerialCommunicator(sp);
}
public void DataHasBeenReceived(object sender, EventArgs args)
{
    textBox1.Invoke(new EventHandler(delegate { textBox1.Text += "test"; }));
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

冰雪之触 2024-10-26 07:07:31

这就是线程亲和性。 UI 控件不喜欢被除创建它们的线程之外的任何东西接触,但 DataReceived 线程发生在不同的线程中。添加对 Control.Invoke 的调用会将工作项推送回 UI 线程,因此更新的 Text 可以成功。

This is thread-affinity. UI controls don't like to be touched by anything except the thread that created them, but the DataReceived thread happens from a different thread. Adding a call toControl.Invoke pushes an item of work back to the UI thread, so the Text updated can succeed.

翻了热茶 2024-10-26 07:07:31

我不是这方面的专家(可能会有比这更好的答案)。但据我了解,GUI 线程“拥有”您的表单。因此,当您尝试从不同的线程更新它时,您就跨越了流。

Invoke 是一种要求 GUI 线程运行方法的方法。它运行的方法是你的 textBox1.Text += "test";

这个想法是通过调用委托,这将要求 GUI 线程进行更改,而不是仅仅自己更改值。这允许以线程安全的方式完成更改。

这是乔恩·斯基特(Jon Skeet)关于这个问题的一篇好文章:
http://www.yoda.arachsys.com/csharp/threads/winforms.shtml

I am not an expert on this (there will likely be better answers than this). But as I understand it, the GUI thread "owns" your form. So when you try to update it from a different thread you are crossing the streams.

The Invoke is a way to ask the GUI thread to run a method. Method that it runs is your textBox1.Text += "test";

The idea is by invoking a delegate, that will ask the GUI thread to make the change, rather than just changing the value yourself. This allows allow the change to be done in a thread safe manner.

Here is a good article by Jon Skeet on this issue:
http://www.yoda.arachsys.com/csharp/threads/winforms.shtml

不打扰别人 2024-10-26 07:07:31

事件是从事件发生的线程调用的。 (除非另有说明)。

这样想:
当您激活事件时,它实际上被称为函数EventName()。因此,调用事件实际上意味着访问注册到该事件的所有方法并执行它们。
但是,这是在同一线程中以串行方式完成的。

因此,如果事件发生在不是您的 UI 线程的线程中,您将收到剧院错误。

Events are called from the thread where they happen. (Unless specified otherwise).

Think about this way:
When you activate the event, it is actually called as a finction EventName(). So calling an event means actually going to all the methods that were registered to that event and doing them.
But, this is done in the same thread in a serial way.

So if an event happened in a thread that is not your UI thread you'll get theat error.

桃扇骨 2024-10-26 07:07:31

问题是 GUI 组件只接受来自 GUI 线程的修改。因此,当其他线程想要修改 GUI 时,它们必须使用诸如 control.Invoke(...) 之类的措施对修改代码进行排队,这将在 GUI 上尽快对要处理的委托进行排队事件队列,因此是正确的线程。

您遇到的情况是,触发了内置检查之一,而不是控制调用线程确实是正确的线程。这是一种使调试更容易的安全措施(如果它们不存在,您将不得不调试微妙的线程问题......)

The issue is that the GUI components only accepts modifications from the GUI thread. So when other threads want to modify the GUI, then they must queue their modification code using measures like control.Invoke(...) which will queue the delegate to be processed as soon as possible on the GUI event queue, and thus the correct thread.

What you run in to is that one of the built-in checks are fired than controls that the calling thread indeed is the correct thread. It is a security measure that makes debugging easier (if they were not present you would have to debug subtle threading issues instead...)

暖风昔人 2024-10-26 07:07:31

textBox1.Text = "test" 不起作用,因为您是从另一个线程(即 DataHasBeenReceived 事件)调用它,然后是拥有文本框的线程。这通常是应用程序运行并创建 GUI 界面(以及文本框)的线程。 Invoke 之所以有效,是因为该方法切换到 GUI 线程,设置文本并然后切换回 DataHasBeenReceived 事件的线程。

在 Net 1.0 和 1.1 中,您可以从另一个线程使用 GUI 控件,然后再使用拥有它们的线程,但是当线程同时开始访问这些控件时,这会导致很多问题。因此,自 net 2.0 以来,微软改变了这一点。

如果您想知道是否必须使用调用(即是否可以从 GUI 线程或另一个线程调用一个方法),您可以使用属性 InvokeRequired 与 if else 组合。调用比直接操作控件稍微昂贵一些。

textBox1.Text = "test" doesn't work because you are calling it from another thread (i.e. the DataHasBeenReceived event) then the thread who owns the textbox. That's usually the thread in which your application runs and that creates your GUI interface (and thus your textbox). Invoke works because that methods switches to the GUI thread, sets your text and then switches back to the thread of your DataHasBeenReceived event.

In Net 1.0 and 1.1 you could use GUI controls from another thread then then the one that owned them but this resulted in a lot of problems when threads started accessing the controls at the same time. So, since net 2.0 Microsoft changed that.

If you want to know if must use invoke or not (i.e. if a method can be called from the both the GUI thread or another thread), you can use the property InvokeRequired combined with an if else. A invoke call is slightly more expensive then a direct manipulation of the control.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文