在不阻塞 UI 执行的情况下等待几秒钟
Posted
技术标签:
【中文标题】在不阻塞 UI 执行的情况下等待几秒钟【英文标题】:Wait some seconds without blocking UI execution 【发布时间】:2014-04-05 04:20:26 【问题描述】:我想在两条指令之间等待几秒钟,但不会阻塞执行。
例如Thread.Sleep(2000)
不好,因为它会阻塞执行。
这个想法是我调用一个方法,然后等待 X 秒(例如 20 秒)来监听事件的到来。在 20 秒结束时,我应该根据 20 秒内发生的情况进行一些操作。
【问题讨论】:
我们可以看一个例子来说明你的意思吗?这是非常广泛的。 另外,您正在构建一种应用程序吗?控制台、WPF、Winforms、ASP.NET 应用程序或其他什么? 你是总是想等待X秒的事件,还是如果事件来了就做一些操作,如果事件没有在X秒内发生,你可以做一些其他的操作,即像超时? 【参考方案1】:我认为您所追求的是Task.Delay。这不会像 Sleep 那样阻塞线程,这意味着您可以使用异步编程模型使用单个线程来执行此操作。
async Task PutTaskDelay()
await Task.Delay(5000);
private async void btnTaskDelay_Click(object sender, EventArgs e)
await PutTaskDelay();
MessageBox.Show("I am back");
【讨论】:
谢谢,看起来很有趣。我只有一个问题,C# 无法识别异步和等待。你知道为什么吗? 它们是 .Net 框架的新增功能,并在 Visual Studio 2012 和 2013 的 4.5 版中添加。如果有帮助,nuget.org/packages/Microsoft.Bcl.Async 有一个 nuget 包。 你甚至不需要PutTaskDelay()
,直接拨打await Task.Delay(5000);
就可以了
@Black 对别人的答案做出这样的改变是不合适的。注释很好,但如果其他人更愿意将代码提取到方法中,那是他们的决定。
@Black 这是你的改进版。重点是。如果您想出改进解决方案的方法,请发布评论以供作者决定进行更改,或者发布您的替代方案作为您自己的答案(适当引用衍生作品)。将您的新版本编辑成别人的答案是不合适的。【参考方案2】:
我用:
private void WaitNSeconds(int segundos)
if (segundos < 1) return;
DateTime _desired = DateTime.Now.AddSeconds(segundos);
while (DateTime.Now < _desired)
System.Windows.Forms.Application.DoEvents();
【讨论】:
我有一个卡在 .NET 3.5 的应用程序(无异步/等待)。这是完美的。 如果您无法升级到 .NET 4.5,此解决方案是有效的,但如果不进行细微调整,它将消耗过多的 CPU 资源。详细解释见我的回答。 提防 DoEvents 中隐藏的邪恶。见blog.codinghorror.com/is-doevents-evil或blog.codinghorror.com/is-doevents-evil-revisited 当心!如果您在表单的加载事件处理程序中使用此等待,那么您的表单将仅在等待时间过去后显示。 这会占用 CPU。【参考方案3】:这是使用另一个线程的好例子:
// Call some method
this.Method();
Task.Factory.StartNew(() =>
Thread.Sleep(20000);
// Do things here.
// NOTE: You may need to invoke this to your main thread depending on what you're doing
);
以上代码需要.NET 4.0或以上,否则试试:
ThreadPool.QueueUserWorkItem(new WaitCallback(delegate
Thread.Sleep(20000);
// Do things here
));
【讨论】:
我只是想在这里对我自己的旧答案添加评论,说我更喜欢其他答案中提到的异步方法,因为它们既干净又更好的做法。如果您还没有阅读过异步,我强烈建议您阅读。【参考方案4】:如果您无法将环境升级到 .NET 4.5 以获得对 async 和 await API 的访问权限,Omar 的解决方案是不错的*。也就是说,为了避免性能不佳,应该进行一项重要的更改。应在调用 Application.DoEvents() 之间添加轻微延迟,以防止 CPU 使用过多。通过添加
Thread.Sleep(1);
在调用 Application.DoEvents() 之前,您可以添加这样的延迟(1 毫秒)并阻止应用程序使用所有可用的 cpu 周期。
private void WaitNSeconds(int seconds)
if (seconds < 1) return;
DateTime _desired = DateTime.Now.AddSeconds(seconds);
while (DateTime.Now < _desired)
Thread.Sleep(1);
System.Windows.Forms.Application.DoEvents();
*有关使用 Application.DoEvents() 的潜在陷阱的更详细讨论,请参阅 https://blog.codinghorror.com/is-doevents-evil/。
【讨论】:
在我发布此解决方案时,我没有达到将其作为评论添加到 Omar 解决方案所需的最低信誉。现在我保持这个答案是开放的,因为它涉及格式化的代码。 我从 Fw 3.5 到 4.5.1 开始使用 MVVM 应用程序进行编程,即使使用所有新资源,有时对问题的简单解决方案也是最好的【参考方案5】:如果你不想阻塞,也不想使用多线程,这里有适合你的解决方案: https://msdn.microsoft.com/en-us/library/system.timers.timer(v=vs.110).aspx
UI Thread 没有被阻塞,定时器等待 2 秒再做某事。
这是来自上面链接的代码:
// Create a timer with a two second interval.
aTimer = new System.Timers.Timer(2000);
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += OnTimedEvent;
aTimer.Enabled = true;
Console.WriteLine("Press the Enter key to exit the program... ");
Console.ReadLine();
Console.WriteLine("Terminating the application...");
【讨论】:
很久以前但是我有一个处理 POST 的 asp.net 页面,主要是后端的东西,需要一个计时器,而这个没有阻塞。【参考方案6】:我真的不建议您不要使用 Thread.Sleep(2000)
,原因有很多(其中一些被描述为 here),但最重要的是因为它在调试/测试方面没有用。
我建议使用C# Timer 而不是Thread.Sleep()
。计时器让您可以频繁地执行方法(如果需要)并且在测试中更容易使用!有一个很好的例子说明如何在超链接后面使用计时器 - 只需将“2 秒后发生的情况”的逻辑放入 Timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
方法中即可。
【讨论】:
【参考方案7】:你好,这是我的建议
.......
var t = Task.Run(async () => await Task.Delay(TimeSpan.FromSeconds(Consts.FiveHundred)).ConfigureAwait(false));
//just to wait task is done
t.Wait();
请记住“等待”,否则延迟运行不会影响应用程序
【讨论】:
【参考方案8】:在我的情况下,我需要这样做,因为我已经将一个方法传递给了我正在等待的线程,这导致了锁定,因为 metod 在 GUI 线程上运行并且线程代码有时会调用该方法。
Task<string> myTask = Task.Run(() =>
// Your code or method call
return "Maybe you want to return other datatype, then just change it.";
);
// Some other code maybe...
while (true)
myTask.Wait(10);
Application.DoEvents();
if (myTask.IsCompleted) break;
【讨论】:
永远不要打电话给Application.DoEvents()
。这是邪恶和阴险的。【参考方案9】:
如果可能,首选方法应该是使用异步方式或第二个线程。
如果这不可能或不想要,使用DoEvents()
的任何实现都是一个坏主意,因为它可能会导致问题Possible problems with DoEvents。这主要是关于带有 Winforms 的 DoEvents,但 WPF 中可能存在的陷阱是相同的。
然后在 Dispatcher 上放置一个具有所需睡眠时间的帧:
using System;
using System.Threading;
using System.Windows.Threading;
public static void NonBlockingSleep(int timeInMilliseconds)
DispatcherFrame df = new DispatcherFrame();
new Thread((ThreadStart)(() =>
Thread.Sleep(TimeSpan.FromMilliseconds(timeInMilliseconds));
df.Continue = false;
)).Start();
Dispatcher.PushFrame(df);
【讨论】:
【参考方案10】:您可以使用本地时间。例如等待 10 秒:
LocalTime tenSecondsLater = LocalTime.now().plus(10, ChronoUnit.SECONDS);
while (tenSecondsLater.isAfter(LocalTime.now()))
// continue
【讨论】:
【参考方案11】:查看System.Threading.Timer
类。我想这就是你要找的。p>
code example on MSDN 似乎表明这个类与你正在尝试做的非常相似(在特定时间后检查状态)。
MSDN 链接中提到的代码示例:
using System;
using System.Threading;
class TimerExample
static void Main()
// Create an AutoResetEvent to signal the timeout threshold in the
// timer callback has been reached.
var autoEvent = new AutoResetEvent(false);
var statusChecker = new StatusChecker(10);
// Create a timer that invokes CheckStatus after one second,
// and every 1/4 second thereafter.
Console.WriteLine("0:h:mm:ss.fff Creating timer.\n",
DateTime.Now);
var stateTimer = new Timer(statusChecker.CheckStatus,
autoEvent, 1000, 250);
// When autoEvent signals, change the period to every half second.
autoEvent.WaitOne();
stateTimer.Change(0, 500);
Console.WriteLine("\nChanging period to .5 seconds.\n");
// When autoEvent signals the second time, dispose of the timer.
autoEvent.WaitOne();
stateTimer.Dispose();
Console.WriteLine("\nDestroying timer.");
class StatusChecker
private int invokeCount;
private int maxCount;
public StatusChecker(int count)
invokeCount = 0;
maxCount = count;
// This method is called by the timer delegate.
public void CheckStatus(Object stateInfo)
AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
Console.WriteLine("0 Checking status 1,2.",
DateTime.Now.ToString("h:mm:ss.fff"),
(++invokeCount).ToString());
if(invokeCount == maxCount)
// Reset the counter and signal the waiting thread.
invokeCount = 0;
autoEvent.Set();
// The example displays output like the following:
// 11:59:54.202 Creating timer.
//
// 11:59:55.217 Checking status 1.
// 11:59:55.466 Checking status 2.
// 11:59:55.716 Checking status 3.
// 11:59:55.968 Checking status 4.
// 11:59:56.218 Checking status 5.
// 11:59:56.470 Checking status 6.
// 11:59:56.722 Checking status 7.
// 11:59:56.972 Checking status 8.
// 11:59:57.223 Checking status 9.
// 11:59:57.473 Checking status 10.
//
// Changing period to .5 seconds.
//
// 11:59:57.474 Checking status 1.
// 11:59:57.976 Checking status 2.
// 11:59:58.476 Checking status 3.
// 11:59:58.977 Checking status 4.
// 11:59:59.477 Checking status 5.
// 11:59:59.977 Checking status 6.
// 12:00:00.478 Checking status 7.
// 12:00:00.980 Checking status 8.
// 12:00:01.481 Checking status 9.
// 12:00:01.981 Checking status 10.
//
// Destroying timer.
【讨论】:
你能提供一个小样本吗? @JeroenVannevel 我帖子的链接中有一个示例。我缺少什么可以补充? @JeroenVannevel 您应该添加一个示例,以便即使链接无效,您的答案仍会提供一个值。此外,“查看 foobar”本身仅提供很少的价值。 我不会将此答案标记为“不是答案”,但有些用户已经这样做了。以上是关于在不阻塞 UI 执行的情况下等待几秒钟的主要内容,如果未能解决你的问题,请参考以下文章