【问题标题】:How to asynchronously wait for x seconds and execute something then?如何异步等待 x 秒然后执行某些操作?
【发布时间】:2012-02-25 02:32:43
【问题描述】:
我知道 C# 和 Windows 窗体中有 Thread.Sleep 和 System.Windows.Forms.Timer 和 Monitor.Wait。我似乎无法弄清楚如何等待 X 秒然后做其他事情 - 不锁定线程。
我有一个带有按钮的表单。单击按钮时,计时器将启动并等待 5 秒。在这 5 秒之后,表单上的一些其他控件将变为绿色。使用Thread.Sleep 时,整个应用程序将在 5 秒内无响应 - 那么我该如何“在 5 秒后执行某项操作”?
【问题讨论】:
标签:
c#
winforms
forms
sleep
wait
【解决方案1】:
(从 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 中的“异步和等待”。
【解决方案3】:
您可以启动一个异步任务来执行您的操作:
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()));
});
[/EDIT]
【解决方案4】:
您可以按照您希望的方式等待 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.
});
});
【解决方案5】:
你看错了。
单击该按钮,它会启动一个间隔为 x 秒的计时器。当它们启动时,它的事件处理程序会执行任务。
那么你不想发生什么。
当 x 秒过去了。?
任务正在执行时?
例如,如果您不希望在延迟和任务完成之前单击按钮。在按钮单击处理程序中禁用它,并在任务完成时启用它。
如果您想要的只是在任务之前有 5 秒的延迟,那么您应该将启动延迟传递给任务并让它处理它。
【解决方案6】:
您的应用程序挂起,因为您正在主 UI 线程上调用 5 秒睡眠/等待。将 sleep/wait/whatever 操作放在单独的线程中(实际上 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;
}
}
我从here 获取样本(非常值得一读!)。
【解决方案7】:
@eFloh 在标记为答案的帖子中说:
Tick 事件可能在另一个无法修改的线程上执行
你的 gui,你可以抓住这个......
文档不是这么说的。
您在示例代码中使用了 System.Windows.Forms.Timer。
那是一个Forms.Timer。
根据 C# 文档,Timer 事件在 UI 线程上引发。
此 Windows 计时器专为单线程环境而设计,其中
UI 线程用于执行处理。它要求用户
代码有一个可用的 UI 消息泵,并且总是从同一个操作
线程...
另见stackoverflow post here