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

标签 c# .net multithreading winforms timer

玩计时器。 上下文:带有两个标签的 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(); }

    }
  }
}

最佳答案

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

大多数 Control 的方法和属性只能从 UI 线程访问(实际上它们只能从您创建它们的线程访问,但这是另一回事)。这是因为每个线程都必须有自己的消息循环(GetMessage() 按线程过滤消息),然后要使用 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 线程可能很忙或挂起一点)。如果您不需要方法的返回值,您可以简单地将 Invoke 替换为 BeginInvoke,对于 WinForms,您甚至不需要后续调用 EndInvoke :

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

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

它是如何工作的?

GUI Windows 应用程序基于具有消息循环的窗口过程。如果你用纯 C 编写应用程序,你会得到类似这样的东西:

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

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

现在假设您有一个工作线程,如何调用您的主线程?最简单的方法是使用这个底层结构来解决问题。我过度简化了任务,但这些是步骤:

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

WPF 和 WinForms 都使用此方法将消息从线程传递(调度)到 UI 线程。看看this article on MSDN有关多线程和用户界面的更多详细信息,WinForms 隐藏了很多这些详细信息,您不必关心它们,但您可以看一下以了解它在后台的工作原理。

关于c# - 如何从另一个线程调用 UI 方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10170448/

相关文章:

Windows 和 Linux 线程优先级等效

c# - 为什么即使使用 Monitor 时,并非所有成员变量都需要 volatile 以保证线程安全? (为什么这个模型真的有效?)

c# - 成功插入数据库后抛出消息,如果不成功则抛出错误消息

c# - 正则表达式在非捕获组中包含字符?

c# - "Control with id could not be located or a different control is assigned to the same ID after postback."错误

.Net 3.5 和 SQL Server 2005 选择 DAL 技术

.net - 应用程序设置,无需保存到 app.config

c# - 将 VB.NET 转换为 C# 时出现问题

.NET 远程处理线程模型

python - 变量范围(带线程)