none
多线程的一个问题。 RRS feed

  • 问题

  • 实在是想不出办法才来发帖的

    我就贴伪代码吧。。。

    //设计了一个接收消息的库Receiver,作为副线程运行:

    while(true){

         接收消息...

         //引发收到消息事件

         MsgReceived("消息");

    }

    //这是主线程的winform项目,侦听上面的MsgReceived事件:

    rcv.MsgRecieved += new EventHandler(UI_Received);

    //这是在副线程中执行的,不能直接用winform控件

    void UI_Received(string msg){    

        this.Invoke(new MethodInvoker(Do_Rcv));    

    }

    void Do_Rcv(string msg){

         this.textbox1.Text = msg;

    以上代码运行良好,但在UI_Received事件中必须调用invoke才能在主线程上执行相关操作。

    我就想问,能不能在消息接收库中(就是副线程中引发事件的地方),直接把UI_Received方法交给主线程,从而避免在WinForm中Invoke?

    这样的话对于UI程序员就是透明的了。

    (不要说CheckForIllegalCrossThreadCalls属性,我是想在接收库中实现而非界面)


    2013年12月11日 23:18

答案

  • 谢谢回复。

    你的办法确实可行,但我觉得既然是一个类库的话,并不能假定其他程序员是用winform来调用的,更不能假定调用方是Control(因为Control才有Invoke),而且在业务代码中要增加observers.Add似乎有点冗余。

    我注意到System.Windows.Forms.Timer就设计的很好:

    Timer t1 = new Timer();
     t1.Interval = 5000;
     t1.Tick += new EventHandler(t1_Tick);
    t1.Start();

     int count;
    void t1_Tick(object sender, EventArgs e) {

        //不需要额外代码,就能直接在主线程中执行

         this.tTextBox1.Text = (count++).ToString();

    }

    相反,System.Threading.Timer就会报错

    因为 System.Windows.Forms.Timer 的 callback 线程是 UI 线程。它是通过 WM_TIMER 消息实现的。

    其实吧,你说的这个问题有现成的解决方案,你可以学习下 Prims (http://compositewpf.codeplex.com/)的 IEventAggregator 的实现,虽然它是应用于 WPF 程序的,但是原理上是一样的。

    我简单给你写个模型:

    Form worker; // 你的窗体应用程序的主窗体实例,也就是 Application.Run 的 Form 实例。

    List<Action<EventArgs>> subscribers;

    while(true){

         接收消息...

         //引发收到消息事件

         //MsgReceived("消息");

         subscribers.ForEach(o=>worker.Invoke(o(eventArgs)));

    }

    那么在你的主线程 Winform 中需要添加订阅的逻辑:

    subscribers.Add(o=> this.textBox1.Text = o);


    2013年12月12日 2:59

全部回复

  • CheckForIllegalCrossThreadCalls 早就被取消了,你想用也用不了。

    因为你最后调用的方法是 TextBox.Text = msg,也就是说,除了委托 UI 线程执行以外,你还需要一个 TextBox 的实例。

    那么你的消息接收库的代码可能是这样:

    while(true){

         接收消息...

         //引发收到消息事件

         //MsgReceived("消息");

         textBox.Invoke(new MethodInvoker(Do_Rcv));

    }

    因此你需要有个列表用于保存订阅事件的 TextBox 的实例,例如:

    List<TextBox> observers = new List<TextBox>();

    while(true){

         接收消息...

         //引发收到消息事件

         //MsgReceived("消息");

         observers.ForEach(o=>o.Invoke(new MethodInvoker(Do_Rcv)));

    }

    那么在你的主线程 Winform 中需要添加订阅的逻辑:

    observers.Add(this.textBox1);

    2013年12月12日 1:24
  • 谢谢回复。

    你的办法确实可行,但我觉得既然是一个类库的话,并不能假定其他程序员是用winform来调用的,更不能假定调用方是Control(因为Control才有Invoke),而且在业务代码中要增加observers.Add似乎有点冗余。

    我注意到System.Windows.Forms.Timer就设计的很好:

    Timer t1 = new Timer();
     t1.Interval = 5000;
     t1.Tick += new EventHandler(t1_Tick);
    t1.Start();

     int count;
    void t1_Tick(object sender, EventArgs e) {

        //不需要额外代码,就能直接在主线程中执行

         this.tTextBox1.Text = (count++).ToString();

    }

    相反,System.Threading.Timer就会报错

    2013年12月12日 1:53
  • 谢谢回复。

    你的办法确实可行,但我觉得既然是一个类库的话,并不能假定其他程序员是用winform来调用的,更不能假定调用方是Control(因为Control才有Invoke),而且在业务代码中要增加observers.Add似乎有点冗余。

    我注意到System.Windows.Forms.Timer就设计的很好:

    Timer t1 = new Timer();
     t1.Interval = 5000;
     t1.Tick += new EventHandler(t1_Tick);
    t1.Start();

     int count;
    void t1_Tick(object sender, EventArgs e) {

        //不需要额外代码,就能直接在主线程中执行

         this.tTextBox1.Text = (count++).ToString();

    }

    相反,System.Threading.Timer就会报错

    因为 System.Windows.Forms.Timer 的 callback 线程是 UI 线程。它是通过 WM_TIMER 消息实现的。

    其实吧,你说的这个问题有现成的解决方案,你可以学习下 Prims (http://compositewpf.codeplex.com/)的 IEventAggregator 的实现,虽然它是应用于 WPF 程序的,但是原理上是一样的。

    我简单给你写个模型:

    Form worker; // 你的窗体应用程序的主窗体实例,也就是 Application.Run 的 Form 实例。

    List<Action<EventArgs>> subscribers;

    while(true){

         接收消息...

         //引发收到消息事件

         //MsgReceived("消息");

         subscribers.ForEach(o=>worker.Invoke(o(eventArgs)));

    }

    那么在你的主线程 Winform 中需要添加订阅的逻辑:

    subscribers.Add(o=> this.textBox1.Text = o);


    2013年12月12日 2:59