一尘不染

如何从另一个线程调用UI方法

c#

与计时器一起玩。上下文:具有两个标签的winforms。

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

我在MSDN上看过这里,但是有没有更简单的方法!

这是winforms代码:

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

    }
  }
}

阅读 460

收藏
2020-05-19

共1个答案

一尘不染

我想您的代码只是一个测试,所以我不会讨论您如何使用计时器。这里的问题是如何使用计时器回调中的用户界面控件执行某些操作。

的大多数Control方法和属性只能从UI线程访问(实际上,只能从创建它们的线程访问它们,但这是另一回事了)。这是因为每个线程都必须有自己的消息循环(GetMessage()按线程过滤出消息),然后再使用a做某事,因此Control必须将消息从线程分派到

线程。在.NET中很容易因为每一个Control继承了几个用于此目的的方法:Invoke/BeginInvoke/EndInvoke。要知道执行线程是否必须调用那些方法,您具有属性InvokeRequired。只需更改此代码即可使其起作用:

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):

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线程可能很忙或挂了一段时间)。如果不需要方法的返回值,则可以简单地替换InvokeBeginInvoke,对于WinForms甚至不需要后续调用EndInvoke

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

如果需要返回值,则必须处理通常的IAsyncResult接口。

怎么运行的?

GUI Windows应用程序基于带有消息循环的窗口过程。如果您使用纯C语言编写应用程序,则将具有以下内容:

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

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

现在假设您有一个工作线程,如何 调用 主线程?最简单的方法是使用此基础结构完成操作。我简化了任务,但是这些步骤是:

  • 创建要调用的函数(线程安全)队列(SO上的一些示例)。
  • 将自定义消息发布到窗口过程。如果将此队列作为优先级队列,则甚至可以决定这些调用的优先级(例如,来自工作线程的进度通知的优先级可能低于警报通知的优先级)。
  • 在窗口过程(在switch / case语句内部)中,您将 了解 该消息,然后可以窥视该函数以从队列中调用并调用它。

WPF和WinForms都使用此方法将消息从线程传递(调度)到UI线程。请参阅MSDN上的这篇文章,以获取有关多个线程和用户界面的更多详细信息,WinForms隐藏了许多这些详细信息,您不必理会它们,但是您可以看看它在幕后如何工作。

2020-05-19