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

问题描述

与计时器一起玩.上下文:一个带有两个标签的 winform.

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

我想看看 System.Timers.Timer 是如何工作的,所以我没有使用表单计时器.我知道表单和 myTimer 现在将在不同的线程中运行.有没有一种简单的方法可以用以下形式表示 lblValue 上的经过时间?

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?

我在 MSDN 但有没有更简单的方法!

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

这是winforms代码:

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.

Control 的大部分方法和属性只能从 UI 线程访问(实际上它们只能从您创建它们的线程访问,但这是另一回事).这是因为每个线程都必须有自己的消息循环(GetMessage() 按线程过滤掉消息)然后要使用 Control 执行某些操作,您必须从中分派消息您的线程到线程.在 .NET 中,这很容易,因为每个 Control 都为此目的继承了几个方法:Invoke/BeginInvoke/EndInvoke.要知道执行线程是否必须调用那些方法,您拥有 InvokeRequired 属性.只需更改您的代码即可使其正常工作:

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";
    }));
}

请查看 MSDN 以获取您可以从任何线程调用的方法列表,就像参考一样,您始终可以调用 InvalidateBeginInvokeEndInvokeInvoke 方法和读取InvokeRequired 属性.通常这是一种常见的使用模式(假设 this 是从 Control 派生的对象):

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
    }
}

请注意,当前线程将阻塞,直到 UI 线程完成方法执行.如果线程的时间很重要,这可能是一个问题(不要忘记 UI 线程可能很忙或挂了一点).如果您不需要方法的返回值,您可以简单地将 Invoke 替换为 BeginInvoke,对于 WinForms,您甚至不需要后续调用 EndInvoke:

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
    }
}

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

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

GUI Windows 应用程序基于带有消息循环的窗口过程.如果你用普通的 C 语言编写一个应用程序,你会得到这样的结果:

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);
}

通过这几行代码,您的应用程序等待消息,然后将消息传递给窗口过程.窗口过程是一个很大的 switch/case 语句,您可以在其中检查您知道的消息 (WM_) 并以某种方式处理它们(您为 WM_PAINT 绘制窗口,然后退出WM_QUIT 等的申请).

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:

  • 创建要调用的(线程安全)函数队列(一些示例在这里).
  • 向窗口过程发布自定义消息.如果您将此队列设为优先队列,那么您甚至可以决定这些调用的优先级(例如,来自工作线程的进度通知的优先级可能低于警报通知).
  • 在窗口过程中(在 switch/case 语句中),您理解该消息,然后您可以查看从队列中调用并调用它的函数.
  • 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.

WPF 和 WinForms 都使用此方法将消息从线程传递(调度)到 UI 线程.请查看 MSDN 上的这篇文章,了解有关多线程和在用户界面中,WinForms 隐藏了很多这些细节,您无需处理它们,但您可以查看一下它的内部工作原理.

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.

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

07-29 12:56