【问题标题】:How to display the time elapsed in a label如何在标签中显示经过的时间
【发布时间】:2021-02-15 07:41:35
【问题描述】:

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

我有一个执行耗时功能的按钮。因此,单击按钮应在标签中以 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 事件仅在按钮事件下的完整代码被触发后才会触发。也就是说,tick 事件在通过 StopClock 函数后被触发!我不知道为什么它应该是那样的!

基本上2个问题:

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

  2. 为什么我的代码不起作用?

编辑:我在这里使用了System.Windows.Forms Timer,但结果与System.Timers Timer没有任何不同

【问题讨论】:

  • 您确定 t_Tick 只被触发一次吗?你真的调试了吗?
  • @Shai,从 StopClock 功能退出后,它不会连续不断地发射。但是由于我已经给出了一个 if 子句来停止计时器,所以它很容易停止计时器,因此只滴答一次。希望你能得到我

标签: c# .net winforms timer


【解决方案1】:

问题是您的长时间运行的任务也在 UI 线程上运行。因此计时器无法触发和更新 UI,因为线程正忙于处理长时间运行的任务。

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

在代码中:

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

如果以上都不需要,您可以使用 ThreadPool,如 StaWho 的回答。

【讨论】:

  • 好的,在这种情况下,计时器 a 的真正用途是什么?毕竟我们通常在一个线程上执行整个程序对吧?
  • @nawfal:定期运行代码。如果您需要多个线程,则使用多个线程。这与计时器完全正交。
  • 我们经常在一个线程上执行整个程序,因为这样更容易理解和管理。但是,在这种情况下,您希望同时发生两件事:运行长时间运行的函数并更新 UI。您可以手动执行此操作(即在您的函数中偶尔更新 UI)或通过 2 个线程“自动”执行此操作。
  • @Daniel 您应该在回答中提到直接从后台工作人员访问 UI 是不安全的,但所有 UI 访问都需要编组回 UI 线程。
  • @DanielRose 为什么你的代码会出现编译错误。似乎大括号没有正确实现
【解决方案2】:

您的耗时函数阻塞了主线程。您可以使用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();
    }

【讨论】:

  • 使用 BackgroundWorker,您可以显式更新 UI(在 ProgressChanged 处理程序中)。如果不需要,您可以使用 ThreadPool 代替。请注意,在 .NET 4.5 中,由于新的异步 API,它会变得更加容易。
【解决方案3】:

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

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

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

(并删除代码中的 ref,它没有被使用)。

【讨论】:

  • 让我看看,谢谢..我知道 ref,但是当我看到标签上没有任何回应时,我更加谨慎
  • 如何将单独创建的计时器添加到控件集合中?我厌倦了Controls.Add(),但它说 Timer is not valid Control
【解决方案4】:

@Dainel Rose 的回答非常适合我,但前提是处理了无效的跨线程操作。我可以这样做:

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

而在耗时函数运行的Utility类中,

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

感谢所有帮助过的人。我正在标记丹尼尔的答案..

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多