C# 如何在标签中显示经过的时间

C# 如何在标签中显示经过的时间,c#,.net,winforms,timer,C#,.net,Winforms,Timer,应该直截了当的东西不在这里,尽管读了很多书,我还是找不到办法 我有一个按钮,执行一个耗时的功能。因此,点击按钮时,标签上的时间间隔应为500毫秒,以毫秒为单位。当达到预期结果时,我希望计时器停止我不需要标签中的最后时间(消耗的总时间),但标签应动态显示经过的时间。我的代码是: private void btnHistory_Click(object sender, EventArgs e) { Class1 c = new Class1(); c.

应该直截了当的东西不在这里,尽管读了很多书,我还是找不到办法

我有一个按钮,执行一个耗时的功能。因此,点击按钮时,标签上的时间间隔应为500毫秒,以毫秒为单位。当达到预期结果时,我希望计时器停止我不需要标签中的最后时间(消耗的总时间),但标签应动态显示经过的时间。我的代码是:

    private void btnHistory_Click(object sender, EventArgs e)
    {
        Class1 c = new Class1();
        c.StartClock(ref label12);

        Utility.PopulateHistory(dgvRecords_history, _util); //time consuming function

        c.StopClock();
    }
Class1
中,我写下:

    internal void StartClock(ref Label l)
    {
        Timer t = new Timer();
        t.Interval = 500;
        t.Enabled = true;
        t.Tag = l;
        t.Tick += new EventHandler(t_Tick);
        t.Start();
    }

    int i;
    bool stop;
    void t_Tick(object sender, EventArgs e)
    {
        if (stop)
        {
            ((Timer)sender).Stop();
            return;
        }

        ((Label)((Timer)sender).Tag).Text = (++i).ToString();
    }

    internal void StopClock()
    {
        i = 0;
        stop = true;
    }
发生的情况是,
t_Tick
事件仅在触发按钮下的完整代码事件之后才会触发。也就是说,滴答声事件在通过StopClock函数后被触发!我不知道为什么会这样

2个基本问题:

  • 如何以正确的方式满足我的要求来处理这些问题?我知道我应该使用其他内置类来评估性能,但这只是为了显示。对此,理想的方法是什么

  • 为什么我的代码不起作用


  • 编辑:我在此处使用了
    System.Windows.Forms Timer
    ,但结果与
    System.Timers Timer

    没有任何不同。问题是,您的长期运行任务也在UI线程上运行。因此计时器无法启动和更新UI,因为线程正忙于处理长时间运行的任务

    相反,您应该使用来处理长时间运行的任务

    代码:

    private void btnHistory_Click(object sender, EventArgs e) 
    { 
        Class1 c = new Class1(ref label12); 
        c.StartClock(); 
    
        var backgroundWorker = new BackgroundWorker();
        backgroundWorker.DoWork += (s, e) =>
            {
                // time consuming function
                Utility.PopulateHistory(dgvRecords_history, _util);
            };
    
        backgroundWorker.RunWorkerCompleted += (s, e) =>
            {
                c.StopClock();
            };
    
        backgroundWorker.RunWorkerAsync();
    } 
    
    正如ChrisWue所指出的,由于您现在在一个单独的线程中拥有长时间运行的任务,因此它需要调用对UI线程上的UI控件的任何访问

    如果长时间运行的任务只需要从UI启动一些数据,则可以将这些数据作为
    RunWorkerAsync()
    的参数传递。如果需要将一些结果数据输出到UI,可以在
    RunWorkerCompleted
    事件的处理程序中执行。如果您偶尔需要在进展过程中更新UI,您可以在
    ProgressChanged
    事件的处理程序中进行更新,在DoWork处理程序中调用
    ReportProgress()


    如果不需要上述任何一项,您可以使用线程池,如StaWho的回答所示。

    将计时器添加到表单的组件集合中。或者将计时器存储在类中的字段中

    计时器是垃圾收集的,因为当方法返回时它不再可访问

    我不知道您的长时间运行的代码,但我们应该在单独的线程上运行新代码,或者调用Application.DoEvents


    (并删除代码中的ref,因为它未被使用)。

    耗时的函数正在阻塞主线程。您可以使用
    BackgroundWorker
    或以下技巧:

        public Form1()
        {
            InitializeComponent();
            t.Tick +=new EventHandler(t_Tick);
            t.Interval = 500;
        }
    
        int timeElapsed = 0;
        System.Windows.Forms.Timer t = new System.Windows.Forms.Timer();
        private void button1_Click(object sender, EventArgs e)
        {
            t.Start();
            ThreadPool.QueueUserWorkItem((x) =>
            {
                TimeConsumingFunction();
            });
    
        }
    
        void TimeConsumingFunction()
        {
            Thread.Sleep(10000);
            t.Stop();
        }
    
        void t_Tick(object sender, EventArgs e)
        {
            timeElapsed += t.Interval;
            label1.Text = timeElapsed.ToString();
        }
    

    @DainelRose的回答对我来说非常有效,但只有在处理无效的跨线程操作时才有效。我可以这样做:

    private void btnHistory_Click(object sender, EventArgs e) 
    { 
        Class1 c = new Class1(ref label12); 
        c.StartClock(); 
    
        var backgroundWorker = new BackgroundWorker();
        backgroundWorker.DoWork += ((s, e) =>
            {
                // time consuming function
                Utility.PopulateHistory(dgvRecords_history, _util);
            });
    
        backgroundWorker.RunWorkerCompleted += ((s, e) =>
            {
                c.StopClock();
            });
    
        backgroundWorker.RunWorkerAsync();
    } 
    
    在运行耗时函数的
    实用程序
    类中

        internal static void PopulateHistory(DataGridView dgv, Utility util)
        {
            SetDataGridView_History(dgv, util);
        }
    
        delegate void UpdateDataGridView_History(DataGridView dgv, Utility util);
        static void SetDataGridView_History(DataGridView dgv, Utility util)
        {
            if (dgv.InvokeRequired)
            {
                UpdateDataGridView_History updaterDelegate = new UpdateDataGridView_History(SetDataGridView_History);
                ((Form)util._w).Invoke(updaterDelegate, new object[] { dgv, util });
            }
            else
                //code that utilizes UI thread (long running process in my case)
        }
    

    谢谢所有帮忙的人。我在标记丹尼尔的答案。

    你确定têTick只被解雇一次吗?你真的调试过吗?@Shai,没有,它从秒钟功能中出来后,不停地不停地发射。但是由于我已经给出了一个if子句来停止计时器,它很容易停止计时器,因此只会滴答一次。希望你明白我的意思,既然如此,计时器a的真正意义是什么?毕竟我们通常在一个线程上执行整个程序,对吗?@nawfal:定期运行代码。如果需要多个线程,可以使用多个线程。这与计时器完全正交。我们通常在一个线程上执行整个程序,因为它更容易理解和管理。但是,在这种情况下,您希望同时发生两件事:运行长期运行的函数和更新UI。您可以手动(即在您的函数中偶尔更新UI)或“自动”执行此操作通过2个线程。@Daniel您应该在回答中提到,直接从后台工作程序访问UI是不安全的,但所有UI访问都需要封送回UI线程。@DanielRose为什么您的代码会给出编译错误。似乎花括号没有正确实现请让我注意一下,谢谢..我知道ref,但当我看到标签上没有响应时,我加倍小心了如何向控件集合添加单独创建的计时器?我厌倦了
    控件。Add()
    ,但是它说计时器不是有效的控件。对于BackgroundWorker,您可以显式地更新UI(在ProgressChanged处理程序中)。如果不需要,可以使用线程池。请注意,在.NET4.5中,由于使用了新的异步API,这将更加容易。