【问题标题】:Wait some seconds without blocking UI execution在不阻塞 UI 执行的情况下等待几秒钟
【发布时间】:2014-04-05 04:20:26
【问题描述】:

我想在两条指令之间等待几秒钟,但不会阻塞执行。

例如Thread.Sleep(2000)不好,因为它会阻塞执行。

这个想法是我调用一个方法,然后等待 X 秒(例如 20 秒)来监听事件的到来。在 20 秒结束时,我应该根据 20 秒内发生的情况进行一些操作。

【问题讨论】:

  • 我们可以看一个例子来说明你的意思吗?这是非常广泛的。
  • 另外,您正在构建一种应用程序吗?控制台、WPF、Winforms、ASP.NET 应用程序或其他什么?
  • 你是总是想等待X秒的事件,还是如果事件来了就做一些操作,如果事件没有在X秒内发生,你可以做一些其他的操作,即像超时?

标签: c# timer wait


【解决方案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 资源。详细解释见我的回答。
  • 当心!如果您在表单的加载事件处理程序中使用此等待,那么您的表单将仅在等待时间过去后显示。
  • 这会占用 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”本身仅提供很少的价值。
          • 我不会将此答案标记为“不是答案”,但有些用户已经这样做了。
          猜你喜欢
          • 1970-01-01
          • 2015-09-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多