在不阻塞 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 执行的情况下等待几秒钟的主要内容,如果未能解决你的问题,请参考以下文章

让lua脚本等待/暂停/睡眠/阻塞几秒钟的最简单方法?

让lua脚本等待/暂停/睡眠/阻塞几秒钟的最简单方法?

如何在不阻塞 UI 的情况下创建 UIView

如何在不冻结 GUI 的情况下在单个插槽中实现阻塞进程?

如何在不阻塞 UI 的情况下正确使用 MagicalRecord 从 Web 服务导入数据

Flutter:如何在不阻塞 UI 的情况下异步地从资产中读取文件