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

C# 如何从另一个线程调用UI方法,c#,.net,multithreading,winforms,timer,C#,.net,Multithreading,Winforms,Timer,玩计时器。 上下文:带有两个标签的winforms 我想看看System.Timers.Timer是如何工作的,所以我没有使用表单计时器。 我知道表单和myTimer现在将在不同的线程中运行。 是否有一种简单的方法可以用以下形式表示lblValue上经过的时间 我已经看过了,但有没有更简单的方法 以下是winforms代码: using System.Timers; namespace Ariport_Parking { public partial class AirportParkin

玩计时器。 上下文:带有两个标签的winforms

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

我已经看过了,但有没有更简单的方法

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

    }
  }
}
使用系统定时器;
停车场
{
公共部分类机场停车场:表格
{
//表单的实例变量
System.Timers.Timer myTimer;
int ElapsedCounter=0;
int MaxTime=5000;
int elapsedTime=0;
静态长度=100;
公共机场停车场
{
初始化组件();
保持时间();
lblValue.Text=“你好”;
}
//计时方法
公共无效保留时间(){
myTimer=新系统.Timers.Timer(滴答声长度);
myTimer.appeased+=新的ElapsedEventHandler(myTimer\u appeased);
myTimer.AutoReset=true;
myTimer.Enabled=true;
myTimer.Start();
}
void myTimer_已过(对象myObject、事件args myEventArgs){
myTimer.Stop();
ElapsedCounter+=1;
elapsedTime+=滴答声长度;
if(elapsedTime
我想您的代码只是一个测试,所以我不会讨论您如何使用计时器。这里的问题是如何在计时器回调中使用用户界面控件

大多数
控件的方法和属性只能从UI线程访问(实际上只能从创建它们的线程访问它们,但这是另一回事)。这是因为每个线程都必须有自己的消息循环(
GetMessage()
按线程过滤消息),然后要使用
控件
执行操作,必须将消息从线程分派到主线程。在.NET中,这很容易,因为每个
控件
为此继承了两个方法:
Invoke/BeginInvoke/EndInvoke
。要知道执行线程是否必须调用这些方法,您需要属性
invokererequired
。只需使用以下命令更改代码即可:

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";
    }));
}
请注意,当前线程将阻塞,直到UI线程完成方法执行。如果线程的计时很重要,这可能是一个问题(不要忘记UI线程可能会很忙或挂起一点)。如果您不需要方法的返回值,只需将
Invoke
替换为
BeginInvoke
,对于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);
}
使用这几行代码,应用程序等待消息,然后将消息传递给窗口过程。窗口过程是一个大的开关/案例语句,您可以检查您知道的消息(
WM\u
),并以某种方式处理它们(为
WM\u-paint
绘制窗口,为
WM\u-quit
退出应用程序等等)

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

  • 创建一个(线程安全的)要调用的函数队列(一些示例)
  • 将自定义消息发布到窗口过程。如果将此队列设为优先级队列,则您甚至可以决定这些调用的优先级(例如,来自工作线程的进度通知的优先级可能低于报警通知)
  • 在窗口过程(在switch/case语句中)中,您了解该消息,然后可以从队列中查看要调用的函数并调用它
WPF和WinForms都使用此方法将消息从线程传递(分派)到UI线程。查看以了解有关多线程和用户界面的更多详细信息,WinForms隐藏了许多这些详细信息,您不必关心它们,但您可以查看以了解它是如何在后台工作的。

首先,在Windows窗体(和大多数框架)中,只能访问控件(除非记录为“线程安全的”)通过UI线程

因此,回调中的this.lblElapsedTime.Text=…
显然是错误的。看一看

第二,你应该使用和来计算你的时间

未经测试:

DateTime startTime = DateTime.Now;

void myTimer_Elapsed(...) {
  TimeSpan elapsed = DateTime.Now - startTime;
  this.lblElapsedTime.BeginInvoke(delegate() {
    this.lblElapsedTime.Text = elapsed.ToString();
  });
}

正如被问到的,这里是我的答案,检查跨线程调用,同步变量更新,不停止和启动计时器,也不使用计时器计算经过的时间

编辑修复了
BeginInvoke
呼叫。我已经使用一个通用的
操作完成了跨线程调用,这允许传递发送方和eventargs。如果未使用这些方法(如此处所示),则使用
MethodInvoker
更有效,但我怀疑需要将处理移到无参数方法中

public partial class AirportParking : Form
{
    private Timer myTimer = new Timer(100);
    private int elapsedCounter = 0;
    private readonly DateTime startTime = DateTime.Now;

    private const string EvenText = "hello";
    private const string OddText = "hello world";

    public AirportParking()
    {
        lblValue.Text = EvenText;
        myTimer.Elapsed += MyTimerElapsed;
        myTimer.AutoReset = true;
        myTimer.Enabled = true;
        myTimer.Start();
    }

    private void MyTimerElapsed(object sender,EventArgs myEventArgs)
    {
        If (lblValue.InvokeRequired)
        {
            var self = new Action<object, EventArgs>(MyTimerElapsed);
            this.BeginInvoke(self, new [] {sender, myEventArgs});
            return;   
        }

        lock (this)
        {
            lblElapsedTime.Text = DateTime.Now.SubTract(startTime).ToString();
            elapesedCounter++;
            if(elapsedCounter % 2 == 0)
            {
                lblValue.Text = EvenText;
            }
            else
            {
                lblValue.Text = OddText;
            }
        }
    }
}
公共部分类机场停车场:表格
{
专用定时器myTimer=新定时器(100);
私有内部elapsedCounter=0;
private readonly DateTime startTime=DateTime.Now;
private const string EvenText=“hello”;
private const string OddText=“hello world”;
公共机场停车场
{
lblValue.Text=EvenText;
myTimer.Appeased+=MyTimeRecursed;
public partial class AirportParking : Form
{
    private Timer myTimer = new Timer(100);
    private int elapsedCounter = 0;
    private readonly DateTime startTime = DateTime.Now;

    private const string EvenText = "hello";
    private const string OddText = "hello world";

    public AirportParking()
    {
        lblValue.Text = EvenText;
        myTimer.Elapsed += MyTimerElapsed;
        myTimer.AutoReset = true;
        myTimer.Enabled = true;
        myTimer.Start();
    }

    private void MyTimerElapsed(object sender,EventArgs myEventArgs)
    {
        If (lblValue.InvokeRequired)
        {
            var self = new Action<object, EventArgs>(MyTimerElapsed);
            this.BeginInvoke(self, new [] {sender, myEventArgs});
            return;   
        }

        lock (this)
        {
            lblElapsedTime.Text = DateTime.Now.SubTract(startTime).ToString();
            elapesedCounter++;
            if(elapsedCounter % 2 == 0)
            {
                lblValue.Text = EvenText;
            }
            else
            {
                lblValue.Text = OddText;
            }
        }
    }
}
using System.Timers;

namespace Ariport_Parking
{
  public partial class AirportParking : Form
  {

    //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    //instance variables of the form
    System.Timers.Timer myTimer;

    private const string EvenText = "hello";
    private const string OddText = "hello world";

    static int tickLength = 100; 
    static int elapsedCounter;
    private int MaxTime = 5000;
    private TimeSpan elapsedTime; 
    private readonly DateTime startTime = DateTime.Now; 
    //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


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

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

    using (System.Timers.Timer myTimer = new System.Timers.Timer(tickLength))
    {  
           myTimer.Elapsed += new ElapsedEventHandler(myTimer_Elapsed);
           myTimer.AutoReset = true;
           myTimer.Enabled = true;
           myTimer.Start(); 
    }  

    }

    private void myTimer_Elapsed(Object myObject,EventArgs myEventArgs){

        elapsedCounter++;
        elapsedTime = DateTime.Now.Subtract(startTime);

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

                if (elapsedCounter % 2 == 0)
                    this.lblValue.Text = EvenText;
                else
                    this.lblValue.Text = OddText;
            })); 
        } 
        else {myTimer.Stop();}
      }
  }
}
private void InvokeUI(Action a)
{
    this.BeginInvoke(new MethodInvoker(a));
}
InvokeUI(() => { 
   Label1.Text = "Super Cool";
});