如何异步等待x秒钟并执行某些操作?

46
我知道C#和Windows Forms中有Thread.SleepSystem.Windows.Forms.TimerMonitor.Wait。但我似乎无法弄清楚如何等待X秒然后执行其他操作 - 而不锁定线程。
我有一个带有按钮的表单。单击该按钮后,计时器将启动并等待5秒钟。在这5秒钟后,表单上的其他控件将变为绿色。使用Thread.Sleep会导致整个应用程序在5秒钟内无响应 - 那么我该如何“等待5秒钟后执行某些操作”?

我认为你要找的是 System.Timers... 我认为你不需要为你所尝试的操作设置 CWND 计时器:请参阅 http://msdn.microsoft.com/en-us/library/0tcs6ww8(v=VS.90).aspx 以获取示例。它应该可以解决问题。 - Ahmed Masud
7个回答

50

(从Ben的评论中转录)

只需使用System.Windows.Forms.Timer。将计时器设置为5秒,并处理Tick事件。当事件触发时,执行该操作。

...并在执行工作之前禁用计时器(IsEnabled=false),以抑制第二次触发。

Tick事件可能在无法修改您的gui的另一个线程上执行,您可以捕获它:

private System.Windows.Forms.Timer myTimer = new System.Windows.Forms.Timer();

    private void StartAsyncTimedWork()
    {
        myTimer.Interval = 5000;
        myTimer.Tick += new EventHandler(myTimer_Tick);
        myTimer.Start();
    }

    private void myTimer_Tick(object sender, EventArgs e)
    {
        if (this.InvokeRequired)
        {
            /* Not on UI thread, reenter there... */
            this.BeginInvoke(new EventHandler(myTimer_Tick), sender, e);
        }
        else
        {
            lock (myTimer)
            {
                /* only work when this is no reentry while we are already working */
                if (this.myTimer.Enabled)
                {
                    this.myTimer.Stop();
                    this.doMyDelayedWork();
                    this.myTimer.Start(); /* optionally restart for periodic work */
                }
            }
        }
    }

仅为完整性考虑:使用async/await,可以非常简单地延迟执行某些操作(一次性的,不需要重复调用):

private async Task delayedWork()
{
    await Task.Delay(5000);
    this.doMyDelayedWork();
}

//This could be a button click event handler or the like */
private void StartAsyncTimedWork()
{
    Task ignoredAwaitableResult = this.delayedWork();
}

更多信息请参见MSDN中的“async和await”。


更完整的内容: 根据您的框架,您很有可能拥有可以在内部处理调用的DispatcherTimer类(WPF变体)。 (在ms文档中找到详细信息)


45

14
您可以启动一个异步任务来执行您的操作:
Task.Factory.StartNew(()=>
{
    Thread.Sleep(5000);
    form.Invoke(new Action(()=>DoSomething()));
});

[编辑]

要传递区间,只需将其存储在变量中:

int interval = 5000;
Task.Factory.StartNew(()=>
{
    Thread.Sleep(interval);
    form.Invoke(new Action(()=>DoSomething()));
});

[/编辑]


8

您可以按照自己希望的方式等待UI线程。

Task.Factory.StartNew(async() =>
{
    await Task.Delay(2000);

    // it only works in WPF
    Application.Current.Dispatcher.Invoke(() =>
    {
        // Do something on the UI thread.
    });
});

如果你使用的是 .Net Framework 4.5 或更高版本,你可以像下面这样使用 Task.Run 代替 Task.Factory.StartNew
int millisecondsDelay = 2000;

Task.Run(async() =>
{
    await Task.Delay(millisecondsDelay);

    // it only works in WPF
    Application.Current.Dispatcher.Invoke(() =>
    {
        // Do something on the UI thread.
    });
});

让UI线程等待会导致糟糕的用户体验,为什么不在另一个线程中执行呢? - Mofor Emmanuel

1

你的应用程序挂起是因为在主UI线程上调用了5秒的sleep/wait。将sleep/wait/任何操作放在单独的线程中(实际上System.Windows.Forms.Timer应该可以为您完成此操作),当它完成时,调用将某个控件变为绿色的操作。记得检查InvokeRequired。这里是一个简短的示例(SetText可以从另一个线程调用,如果是这样,调用将在文本框所在的主UI线程上调用):

private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{    
    SetTextCallback d = new SetTextCallback(SetText);
    this.Invoke(d, new object[] { text });
}
else
{
    this.textBox1.Text = text;
}
}

我从这里获取了样例(非常值得一读!)。


1

@eFloh在被标记为答案的帖子中说:

Tick事件可能在另一个无法修改GUI的线程上执行,您可以捕获它...

那不是文档所说的。
您在示例代码中使用了System.Windows.Forms.Timer。
那是一个Forms.Timer
根据C#文档,计时器事件在UI线程上引发。

此Windows计时器设计用于单线程环境,其中使用UI线程执行处理。它要求用户代码有一个可用的UI消息泵,并始终从同一线程操作...

还请参见stackoverflow post here


1

你的理解方式有误。

点击按钮会启动一个定时器,时间间隔为x秒。当时间到达时,它的事件处理程序会执行任务。

那么你不想发生什么。

在x秒内经过了多久?

在任务执行期间?

例如,如果你不希望在延迟和任务完成之前单击该按钮,请在按钮单击处理程序中将其禁用,并在任务完成时启用它。

如果你只想要一个五秒的延迟,然后执行该任务,那么你应该将起始延迟传递给任务,让它来处理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接