如何从另一个线程调用用户界面的方法 [英] How to invoke a UI method from another thread

查看:211
本文介绍了如何从另一个线程调用用户界面的方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

打一轮定时器。 背景:一个WinForms有两个标签。

我想看看 System.Timers.Timer的的作品,所以我还没有使用的表单计时器。 据我所知,形式和myTimer现在将运行在不同的线程。 有没有一种简单的方法来重新present经过时间 lblValue 下面的表格?

我在这里看着 MSDN 但有一个更简单的方法!

这里的WinForms的code:

 使用System.Timers;

命名空间Ariport_Parking
{
  公共部分类AirportParking:表
  {
    //实例的形式的变量
    System.Timers.Timer的myTimer;
    INT ElapsedCounter = 0;

    INT MAXTIME = 5000;
    INT elapsedTime = 0;
    静态INT tickLength = 100;

    公共AirportParking()
    {
        的InitializeComponent();
        keepingTime();
        lblValue.Text =你好;
    }

    //方法保持时间
    公共无效keepingTime(){

        myTimer =新System.Timers.Timer的(tickLength);
        myTimer.Elapsed + =新ElapsedEventHandler(myTimer_Elapsed);

        myTimer.AutoReset = TRUE;
        myTimer.Enabled = TRUE;

        myTimer.Start();
    }


    无效myTimer_Elapsed(对象myObject的,EventArgs的myEventArgs){

        myTimer.Stop();
        ElapsedCounter + = 1;
        elapsedTime + = tickLength;

        如果(elapsedTime< MAXTIME)
        {
            this.lblElapsedTime.Text = elapsedTime.ToString();

            如果(ElapsedCounter%2 == 0)
                this.lblValue.Text =世界你好;
            其他
                this.lblValue.Text =你好;

            myTimer.Start();

        }
        其他
        {myTimer.Start(); }

    }
  }
}
 

解决方案

我猜你的code只是测试,所以我不会讨论什么,你做你的计时器。这里的问题是如何做你的计时器回调内的用户界面控制。

大多数控制的方法和属性只能从UI线程(在现实中,他们可以在其中创建它们只能访问从线程访问,但这是另一个故事)。这是因为每个线程都必须有自己的消息循环(的GetMessage()过滤了线程的消息),然后做一些与控制,你必须从你的线程分派消息的的线程。在.NET中很容易,因为每个控制继承了几个用于此目的的方法:调用/的BeginInvoke / EndInvoke会。要知道,如果执行的线程必须调用那些你拥有的财产 InvokeRequired 的方法。就在这个改变你的code,使其工作:

 如果(elapsedTime< MAXTIME)
{
    this.BeginInvoke(新MethodInvoker(委托
    {
        this.lblElapsedTime.Text = elapsedTime.ToString();

        如果(ElapsedCounter%2 == 0)
            this.lblValue.Text =世界你好;
        其他
            this.lblValue.Text =你好;
    });
}
 

请查看MSDN的,你可以从任何线程调用的方法列表中,只是作为参考,你可以随时拨打的Invalidate 的BeginInvoke EndInvoke会调用方法和阅读 InvokeRequired 属性。一般而言,这是一种常见的使用模式(假设控制派生的对象):

 无效DoStuff(){
    //被称为从一个错误的主题?
    如果(InvokeRequired){
        //派遣到正确的线程,使用的BeginInvoke如果你不需要
        //调用者线程,直到操作完成
        调用(新MethodInvoker(DoStuff));
    } 其他 {
        //做的事情
    }
}
 

请注意,当前的线程将阻塞,直到UI线程完成的方法执行。这可能是一个问题,如果线程的时机是非常重要的(不要忘了,UI线程可能正忙或挂一点点)。如果你并不需要方法的返回值,你可以简单地替换调用的BeginInvoke ,为的WinForms,你甚至不需要后续调用 EndInvoke会

 无效DoStuff(){
    如果(InvokeRequired){
        的BeginInvoke(新MethodInvoker(DoStuff));
    } 其他 {
        //做的事情
    }
}
 

如果您需要返回值,那么你必须处理与通常的IAsyncResult 接口。

它是如何工作?

如果您有兴趣它如何工作的一些细节。一个GUI Windows应用程序是基于其消息循环窗口过程。如果你写在纯C的应用程序,你有这样的事情:

  MSG消息;
而(的GetMessage(安培;消息,NULL,0,0))
{
    的TranslateMessage(&安培;信息);
    在DispatchMessage(&安培;信息);
}
 

通过这几行$ C $的C本的应用程序等待消息,然后将消息传递给窗口过程。窗口过程中,您检查的消息一个大的switch / case语句( WM _ )你知道,你以某种方式处理它们(你画的窗口WM_PAINT ,你辞职申请 WM_QUIT 等)。

现在想象一下你有一个工作线程,你怎么可以的通话的主线程?简单的方法就是用这个底层结构做的伎俩。我过分简单化的任务,但是这些步骤如下:

  • 创建函数来调用(线程安全的)队列(一些例子<一href="http://stackoverflow.com/questions/10717344/c-sharp-creating-function-queue/10717390#10717390">here在SO )。
  • 发表自定义消息到窗口过程。如果你把这个队列优先级队列,那么你甚至可以决定优先考虑这些要求(例如,从工作线程的进度通知可以具有优先级低于警报通知)。
  • 在窗口过程(你的switch / case语句中),您的明白的该消息,那么你可以窥视功能,从队列中调用,并调用它。

WPF和WinForms的使用这种方法来实现(调度)的消息从一个线程到UI线程。看看到这篇文章在MSDN上关于多线程和更多的细节用户界面的WinForms隐藏了许多细节,你不必照顾他们,但你可以看看,了解它的引擎盖下是如何工作的。

Playing round with Timers. Context: a winforms with two labels.

I would like to see how System.Timers.Timer works so I've not used the Forms timer. I understand that the form and myTimer will now be running in different threads. Is there an easy way to represent the elapsed time on lblValue in the following form?

I've looked here on MSDN but is there an easier way !

Here's the winforms code:

using System.Timers;

namespace Ariport_Parking
{
  public partial class AirportParking : Form
  {
    //instance variables of the form
    System.Timers.Timer myTimer;
    int ElapsedCounter = 0;

    int MaxTime = 5000;
    int elapsedTime = 0;
    static int tickLength = 100;

    public AirportParking()
    {
        InitializeComponent();
        keepingTime();
        lblValue.Text = "hello";
    }

    //method for keeping time
    public void keepingTime() {

        myTimer = new System.Timers.Timer(tickLength); 
        myTimer.Elapsed += new ElapsedEventHandler(myTimer_Elapsed);

        myTimer.AutoReset = true;
        myTimer.Enabled = true;

        myTimer.Start();
    }


    void myTimer_Elapsed(Object myObject,EventArgs myEventArgs){

        myTimer.Stop();
        ElapsedCounter += 1;
        elapsedTime += tickLength; 

        if (elapsedTime < MaxTime)
        {
            this.lblElapsedTime.Text = elapsedTime.ToString();

            if (ElapsedCounter % 2 == 0)
                this.lblValue.Text = "hello world";
            else
                this.lblValue.Text = "hello";

            myTimer.Start(); 

        }
        else
        { myTimer.Start(); }

    }
  }
}

解决方案

I guess your code is just a test so I won't discuss about what you do with your timer. The problem here is how to do something with an user interface control inside your timer callback.

Most of Control's methods and properties can be accessed only from the UI thread (in reality they can be accessed only from the thread where you created them but this is another story). This is because each thread has to have its own message loop (GetMessage() filters out messages by thread) then to do something with a Control you have to dispatch a message from your thread to the main thread. In .NET it is easy because every Control inherits a couple of methods for this purpose: Invoke/BeginInvoke/EndInvoke. To know if executing thread must call those methods you have the property InvokeRequired. Just change your code with this to make it works:

if (elapsedTime < MaxTime)
{
    this.BeginInvoke(new MethodInvoker(delegate 
    {
        this.lblElapsedTime.Text = elapsedTime.ToString();

        if (ElapsedCounter % 2 == 0)
            this.lblValue.Text = "hello world";
        else
            this.lblValue.Text = "hello";
    });
}

Please check MSDN for the list of methods you can call from any thread, just as reference you can always call Invalidate, BeginInvoke, EndInvoke, Invoke methods and to read InvokeRequired property. In general this is a common usage pattern (assuming this is an object derived from Control):

void DoStuff() {
    // Has been called from a "wrong" thread?
    if (InvokeRequired) {
        // Dispatch to correct thread, use BeginInvoke if you don't need
        // caller thread until operation completes
        Invoke(new MethodInvoker(DoStuff));
    } else {
        // Do things
    }
}

Note that current thread will block until UI thread completed method execution. This may be an issue if thread's timing is important (do not forget that UI thread may be busy or hung for a little). If you don't need method's return value you may simply replace Invoke with BeginInvoke, for WinForms you don't even need subsequent call to EndInvoke:

void DoStuff() {
    if (InvokeRequired) {
        BeginInvoke(new MethodInvoker(DoStuff));
    } else {
        // Do things
    }
}

If you need return value then you have to deal with usual IAsyncResult interface.

How it works?

A few details if you're interested on how it works. A GUI Windows application is based on the window procedure with its message loops. If you write an application in plain C you have something like this:

MSG message;
while (GetMessage(&message, NULL, 0, 0))
{
    TranslateMessage(&message);
    DispatchMessage(&message);
}

With these few lines of code your application wait for a message and then delivers the message to the window procedure. The window procedure is a big switch/case statement where you check the messages (WM_) you know and you process them somehow (you paint the window for WM_PAINT, you quit your application for WM_QUIT and so on).

Now imagine you have a working thread, how can you call your main thread? Simplest way is using this underlying structure to do the trick. I oversimplify the task but these are the steps:

  • Create a (thread-safe) queue of functions to invoke (some examples here on SO).
  • Post a custom message to the window procedure. If you make this queue a priority queue then you can even decide priority for these calls (for example a progress notification from a working thread may have a lower priority than an alarm notification).
  • In the window procedure (inside your switch/case statement) you understand that message then you can peek the function to call from the queue and to invoke it.

Both WPF and WinForms use this method to deliver (dispatch) a message from a thread to the UI thread. Take a look to this article on MSDN for more details about multiple threads and user interface, WinForms hides a lot of these details and you do not have to take care of them but you may take a look to understand how it works under the hood.

这篇关于如何从另一个线程调用用户界面的方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆