2

我有一个像“ClientSocket.cs”这样的类

  class ClientSocket {
     public delegate void ConnectHandler(object sender, EventArgs e);
     public event ConnectHandler ConnectEvent = delegate { };

     protected void OnConnectEvent(EventArgs e) {
        ConnectHandler ev = ConnectEvent;
        ev(this, e);
    }

  }

还有另一个类“myForm.cs”

public partial class myForm : Form {
    private ClientSocket client;

    private void button1_Click(object sender, EventArgs e) {
        client = new ClientSocket();
        client.ConnectEvent += myForm_OnConnectEvent;

        client.connect();
    }


    // Handler for ConnectEvent
    private void myForm_OnConnectEvent(object sender, EventArgs e) {
        //this.BeginInvoke((MethodInvoker)delegate { writeLog("Connected"); }); 

        writeLog("Connected");
    }

    // Function that write a log string to a TextBox
    public writeLog(string log) {
        guiTextBox.AppendText(log);
    }
  }

这里的问题。我尝试使用 BeginInvoke 调用 writeLog 或直接调用它。有时我在写入 guiTextBox 时会收到 InvalidOperationException。我不明白为什么我会收到那条消息。该事件由 ClientSocket 对象触发,但事件处理程序与主 UI 线程 (myForm) 相关。

我可以避免对班级的每个 EventHandler 使用 BeginInvoke/Invoke 吗?


编辑:我明白有什么区别,现在我试图了解调用事件的最佳实践。

在 BASE 类中引发事件时我是否应该放置 BeginInvoke/Invoke 方法(在这种情况下为 ClientSocket)

    protected void OnConnectEvent(EventArgs e) {
        ConnectHandler ev = ConnectEvent;

        this.BeginInvoke((MethodInvoker)delegate { ev(this, e);});
    }

或者我应该在使用该对象时将其放入该对象并向该处理程序添加一个侦听器

    // Handler for ConnectEvent used in GUI (myForm)
    private void myForm_OnConnectEvent(object sender, EventArgs e) {
        this.BeginInvoke((MethodInvoker)delegate { writeLog("Connected"); }); 
    }

干杯

4

2 回答 2

3

事件处理程序是在 中声明的myForm,但执行处理程序的线程是由ClientSocket类的逻辑定义的。如果这将是后台线程,则将从后台线程引发事件处理程序,因此,您需要BeginInvoke避免跨线程访问控件。

换句话说:任何类型的任何方法的归属都与线程无关,线程将永远执行此方法。这些东西(类型和线程)是平行宇宙。

顺便说一句,您可以替换它:

public delegate void ConnectHandler(object sender, EventArgs e);
public event ConnectHandler ConnectEvent = delegate { };

有了这个:

public event EventHandler ConnectEvent;

无需再创建另一种委托类型。

于 2013-11-11T21:30:52.230 回答
3

里面存在this.BeginInvoke。为了能够做到这一点,必须在具有该方法的对象上调用它(在您的情况下是您的表单)。ClientSocket BeginInvoke

如果您希望在 ClientSocket 类中进行调用,则需要传入Control具有该BeginInvoke功能的 a。

但是,如果我在哪里写这篇文章,我不会这样做。它增加了一个不必要的要求,ClientSocket即您必须有一个Control传入(这称为紧密耦合,您应该在编程中尽量避免它)。就我个人而言,我会让事件在它想要引发的任何线程中传递,让消费者担心做任何特殊的调用(如果他们甚至需要的话)。

这是我将如何编写myForm_OnConnectEvent的,此模式检查我们是否需要调用,如果我们这样做,它会使用相同的参数再次调用该函数,但这次是在 UI 线程上。

// Handler for ConnectEvent used in GUI (myForm)
private void myForm_OnConnectEvent(object sender, EventArgs e) 
{
    if(this.InvokeRequired)
    {
        this.BeginInvoke(new ConnectHandler(myForm_OnConnectEvent), new object[] {sender, e});
        return;
    }

    writeLog("Connected");
}

作为旁注,我不知道writeLog在做什么(顺便说一下,它应该有一个大写字母 W)但如果它不与 UI 交互,你根本不需要进行任何调用。如果它与TextBoxUI 上的 a 或其他东西交互,就是我要调用的地方。

private void myForm_OnConnectEvent(object sender, EventArgs e) 
{
    writeLog("Connected");
}

private void writeLog(string logMessage) 
{
    if(logTextBox.InvokeRequired)
    {
        logTextBox.BeginInvoke(new Action<string>(writeLog), logMessage);
        return;
    }

    var logLine = String.Format("{0:g}: {1}{2}", DateTime.Now, logMessage, Enviorment.NewLine);
    logTextBox.AppendText(logLine);
}
于 2013-11-16T20:04:16.097 回答