文本框未更新

TextBox not being updated

我的 windows 表单上有一个 textBox2(已确认名称和拼写)我将工具箱中的计时器添加到我的 windows 表单中,它被命名为 timer1。我试图让计时器在按下按钮时开始计时,并让 textBox2 显示每个递增的秒数,以便显示整个过程所用时间的 运行 时间。我哪里做错了?

public partial class Form1 : Form
{
    private int duration = 0;
    private void button1_Click(object sender, EventArgs e)
    {
        timer1.Enabled = true;
        timer1.Start();
        First();
        Second();
        Thirds();
        Fourth();
        Fifth();
        timer1.Stop();
    }
    private void timer1_Tick(object sender, EventArgs e)
    {
        duration++;
        textBox2.Text = duration.ToString();
    }
}

不清楚First()Second()等方法代表什么。但是根据其余代码,这些似乎很可能是您尝试计时的长期 运行 任务。

无论如何,问题是在停止计时器之前,您永远不会从 button1_Click() 方法中 return。计时器滴答在 UI 线程中引发,并且只有在 UI 线程未被其他事件的处理阻塞时才能引发。就像 button1Click 事件的处理。

你应该在一个单独的线程中执行你的 long-运行 操作,例如使用 Task。然后计时器将正常工作,或者您甚至可以在 async 方法中使用循环来显示经过的时间,无论您喜欢哪个。

例如:

private int duration = 0;
private async void button1_Click(object sender, EventArgs e)
{
    timer1.Enabled = true;
    timer1.Start();

    await Task.Run(() =>
    {
        First();
        Second();
        Thirds();
        Fourth();
        Fifth();
    });

    timer1.Stop();
}

private void timer1_Tick(object sender, EventArgs e)
{
    duration++;
    textBox2.Text = duration.ToString();
}

备注:

  • C# 5.0 新增的 await 功能在上面的示例中使用。请参阅下文,了解与旧版本的 C# 和 .NET 兼容的不同方法。
  • 当使用await时,包含语句的async方法将执行到"awaited"表达式。此时,线程的控制权交还给 async 方法的调用者。稍后,当等待的表达式最终完成时,线程的控制权 return 返回到 async 方法之前产生的点(即 await 语句)。
  • 在上面,这意味着包装在 Task 中的五个方法在不同的线程中异步执行。当 Task 启动时,button1_Click() 方法实际上 return 发送给它的调用者。如果它们(即 Task)最终完成,button1_Click() 方法的执行将在下一个语句处恢复:timer1.Stop().


async/await 特性使得使用异步操作的代码更容易编写。但即使在此之前,还有其他机制可以做同样的事情。这是一种这样的机制,使用 BackgroundWorker 对象:

private async void button1_Click(object sender, EventArgs e)
{
    BackgroundWorker worker = new BackgroundWorker();

    worker.DoWork += (sender1, e1) =>
    {
        First();
        Second();
        Thirds();
        Fourth();
        Fifth();
    };

    worker.RunWorkerCompleted += (sender1, e1) =>
    {
        timer1.Stop();
    };

    timer1.Enabled = true;
    timer1.Start();

    worker.RunWorkerAsync();
}

备注:

  • 创建 BackgroundWorker 对象时,它会捕获当前同步上下文(如果有),稍后将使用它来引发除 DoWork 之外的所有 BackgroundWorker 事件。
  • DoWork 事件是使用线程池线程引发的,即主 UI 线程以外的某个线程。这允许在 UI 线程上发生事情,即使正在执行 long-运行 工作。
  • 上面的代码可能看起来有点颠倒。那是因为稍后执行的部分实际上是较早声明为订阅 DoWorkRunWorkerCompleted 事件的匿名方法。
  • 声明了那些匿名方法并订阅了它们的事件后,代码实际上启动了计时器,然后是 BackgroundWorker 本身。只有在 RunWorkerAsync() 方法被调用时,worker 才会启动 运行,引发 DoWork 事件。


顺便说一句,虽然我尽可能完整地保留了您的原始代码,但请注意您不需要 both timer1.Enabled = true;timer1.Start()。要么足以启动计时器;要么你可以使用你喜欢的任何一个而忽略另一个。