【问题标题】:Delayed function calls延迟函数调用
【发布时间】:2009-02-13 10:51:41
【问题描述】:

有没有一种很好的简单方法可以在让线程继续执行的同时延迟函数调用?

例如

public void foo()
{
    // Do stuff!

    // Delayed call to bar() after x number of ms

    // Do more Stuff
}

public void bar()
{
    // Only execute once foo has finished
}

我知道这可以通过使用计时器和事件处理程序来实现,但我想知道是否有标准的 c# 方法来实现这一点?

如果有人好奇,这是必需的原因是 foo() 和 bar() 位于不同的(单例)类中,我需要在特殊情况下相互调用。问题是这是在初始化时完成的,所以 foo 需要调用 bar ,它需要一个正在创建的 foo 类的实例......因此延迟调用 bar() 以确保 foo 完全实例化......读回来几乎是糟糕的设计!

编辑

我会根据建议对糟糕的设计提出意见!我一直认为我可以改进系统,但是,这种讨厌的情况在抛出异常时发生,在所有其他时间两个单例很好地共存。我认为我不会乱用讨厌的异步模式,而是要重构其中一个类的初始化。

【问题讨论】:

  • 你需要修复它,但不是使用线程(或任何其他异步实践)
  • 必须使用线程来同步对象初始化是您应该采取另一种方式的标志。 Orchestrator 似乎是更好的选择。
  • 复活! -- 对设计进行评论,您可以选择进行两阶段初始化。借鉴 Unity3D API,有AwakeStart 阶段。在Awake 阶段,你自己配置,在这个阶段结束时所有对象都被初始化。在Start 阶段,对象可以开始相互通信。
  • 接受的答案需要更改

标签: c# function delay


【解决方案1】:

感谢现代 C# 5/6 :)

public void foo()
{
    Task.Delay(1000).ContinueWith(t=> bar());
}

public void bar()
{
    // do stuff
}

【讨论】:

  • 这个答案很棒有两个原因。代码简单,Delay 不会创建线程,也不会像其他 Task.Run 或 Task.StartNew 那样使用线程池......它在内部是一个计时器。
  • 一个不错的解决方案。
  • 还要注意一个稍微干净的 (IMO) 等效版本:Task.Delay(TimeSpan.FromSeconds(1)).ContinueWith(_ => bar());
  • @Zyo 实际上它确实使用了不同的线程。尝试从中访问 UI 元素,它会触发异常。
  • 在 UI 线程上继续的方法如下:Task.Delay(1000).ContinueWith(_ => { Application.Current.Dispatcher.Invoke(() => { bar(); });
【解决方案2】:

我自己一直在寻找类似的东西 - 我想出了以下方法,虽然它确实使用了一个计时器,但它只使用一次来进行初始延迟,并且不需要任何 Sleep 调用.. .

public void foo()
{
    System.Threading.Timer timer = null; 
    timer = new System.Threading.Timer((obj) =>
                    {
                        bar();
                        timer.Dispose();
                    }, 
                null, 1000, System.Threading.Timeout.Infinite);
}

public void bar()
{
    // do stuff
}

(感谢Fred Deschenes 在回调中设置计时器的想法)

【讨论】:

  • 我觉得这通常是延迟函数调用的最佳答案。没有线程,没有后台工作,没有睡眠。计时器非常高效且内存/cpu 明智。
  • @Zyo,感谢您的评论 - 是的,计时器很有效,这种延迟在许多情况下都很有用,尤其是在与您无法控制的东西交互时 - 没有任何支持通知事件。
  • 什么时候丢弃定时器?
  • 在这里恢复一个旧线程,但定时器可以这样处理: public static void CallWithDelay(Action method, int delay) { Timer timer = null; var cb = new TimerCallback((state) => { method(); timer.Dispose(); }); timer = new Timer(cb, null, delay, Timeout.Infinite);编辑:看起来我们不能在 cmets 中发布代码......当你复制/粘贴它时,VisualStudio 应该正确格式化它:P
  • @dodgy_coder 错误。在绑定到委托对象 cb 的 lambda 中使用 timer 局部变量会导致它被提升到匿名存储(闭包实现细节)上,这将导致从 GC 的角度可以访问 Timer 对象只要TimerCallback 委托本身是可访问的。换句话说,Timer 对象保证在线程池调用委托对象之前不会被垃圾回收。
【解决方案3】:

除了同意之前评论者的设计意见之外,没有一个解决方案对我来说足够干净。 .Net 4 提供了DispatcherTask 类,这使得延迟执行在当前线程上非常简单:

static class AsyncUtils
{
    static public void DelayCall(int msec, Action fn)
    {
        // Grab the dispatcher from the current executing thread
        Dispatcher d = Dispatcher.CurrentDispatcher;

        // Tasks execute in a thread pool thread
        new Task (() => {
            System.Threading.Thread.Sleep (msec);   // delay

            // use the dispatcher to asynchronously invoke the action 
            // back on the original thread
            d.BeginInvoke (fn);                     
        }).Start ();
    }
}

对于上下文,我使用它来消除绑定到 UI 元素上的鼠标左键的ICommand。用户正在双击,这造成了各种破坏。 (我知道我也可以使用Click/DoubleClick 处理程序,但我想要一个与ICommands 全面兼容的解决方案。

public void Execute(object parameter)
{
    if (!IsDebouncing) {
        IsDebouncing = true;
        AsyncUtils.DelayCall (DebouncePeriodMsec, () => {
            IsDebouncing = false;
        });

        _execute ();
    }
}

【讨论】:

    【解决方案4】:

    听起来这两个对象的创建及其相互依赖的控制需要外部控制,而不是类本身之间。

    【讨论】:

    • +1,这听起来确实需要某种编排器,也许还需要一个工厂
    【解决方案5】:

    这确实是一个非常糟糕的设计,更不用说单例本身就是一个糟糕的设计。

    但是,如果您确实需要延迟执行,您可以这样做:

    BackgroundWorker barInvoker = new BackgroundWorker();
    barInvoker.DoWork += delegate
        {
            Thread.Sleep(TimeSpan.FromSeconds(1));
            bar();
        };
    barInvoker.RunWorkerAsync();
    

    但是,这将在单独的线程上调用 bar()。如果您需要在原始线程中调用bar(),您可能需要将bar() 调用移动到RunWorkerCompleted 处理程序或使用SynchronizationContext 进行一些黑客攻击。

    【讨论】:

      【解决方案6】:

      好吧,我不得不同意“设计”这一点……但您可能可以使用监视器让一个人知道另一个人何时超过了临界区……

          public void foo() {
              // Do stuff!
      
              object syncLock = new object();
              lock (syncLock) {
                  // Delayed call to bar() after x number of ms
                  ThreadPool.QueueUserWorkItem(delegate {
                      lock(syncLock) {
                          bar();
                      }
                  });
      
                  // Do more Stuff
              } 
              // lock now released, bar can begin            
          }
      

      【讨论】:

        【解决方案7】:
        public static class DelayedDelegate
        {
        
            static Timer runDelegates;
            static Dictionary<MethodInvoker, DateTime> delayedDelegates = new Dictionary<MethodInvoker, DateTime>();
        
            static DelayedDelegate()
            {
        
                runDelegates = new Timer();
                runDelegates.Interval = 250;
                runDelegates.Tick += RunDelegates;
                runDelegates.Enabled = true;
        
            }
        
            public static void Add(MethodInvoker method, int delay)
            {
        
                delayedDelegates.Add(method, DateTime.Now + TimeSpan.FromSeconds(delay));
        
            }
        
            static void RunDelegates(object sender, EventArgs e)
            {
        
                List<MethodInvoker> removeDelegates = new List<MethodInvoker>();
        
                foreach (MethodInvoker method in delayedDelegates.Keys)
                {
        
                    if (DateTime.Now >= delayedDelegates[method])
                    {
                        method();
                        removeDelegates.Add(method);
                    }
        
                }
        
                foreach (MethodInvoker method in removeDelegates)
                {
        
                    delayedDelegates.Remove(method);
        
                }
        
        
            }
        
        }
        

        用法:

        DelayedDelegate.Add(MyMethod,5);
        
        void MyMethod()
        {
             MessageBox.Show("5 Seconds Later!");
        }
        

        【讨论】:

        • 我建议添加一些逻辑以避免计时器每 250 毫秒运行一次。第一:您可以将延迟增加到 500 毫秒,因为您的最小允许间隔是 1 秒。第二:您可以仅在添加新代表时启动计时器,并在没有更多代表时停止它。当无事可做时,没有理由继续使用 CPU 周期。第三:您可以将计时器间隔设置为所有代表的最小延迟。所以它只在需要调用委托时才唤醒,而不是每 250 毫秒唤醒一次以查看是否有事情要做。
        • MethodInvoker 是一个 Windows.Forms 对象。请问有没有可供Web开发人员使用的替代方案?即:与 System.Web.UI.WebControls 不冲突的东西。
        【解决方案8】:

        这适用于旧版本的 .NET
        缺点:将在自己的线程中执行

        class CancellableDelay
            {
                Thread delayTh;
                Action action;
                int ms;
        
                public static CancellableDelay StartAfter(int milliseconds, Action action)
                {
                    CancellableDelay result = new CancellableDelay() { ms = milliseconds };
                    result.action = action;
                    result.delayTh = new Thread(result.Delay);
                    result.delayTh.Start();
                    return result;
                }
        
                private CancellableDelay() { }
        
                void Delay()
                {
                    try
                    {
                        Thread.Sleep(ms);
                        action.Invoke();
                    }
                    catch (ThreadAbortException)
                    { }
                }
        
                public void Cancel() => delayTh.Abort();
        
            }
        

        用法:

        var job = CancellableDelay.StartAfter(1000, () => { WorkAfter1sec(); });  
        job.Cancel(); //to cancel the delayed job
        

        【讨论】:

          【解决方案9】:

          虽然完美的解决方案是让计时器处理延迟的动作。 FxCop 不喜欢间隔时间少于一秒。 我需要延迟我的操作,直到我的 DataGrid 完成按列排序之后。 我认为一次性计时器(AutoReset = false)将是解决方案,并且效果很好。 而且,FxCop 不会让我压制警告!

          【讨论】:

            【解决方案10】:

            除了使用计时器和事件之外,没有标准方法可以延迟对函数的调用。

            这听起来像是延迟调用方法的 GUI 反模式,这样您就可以确定表单已经完成布局。不是个好主意。

            【讨论】:

              【解决方案11】:

              基于 David O'Donoghue 的回答,这里是延迟委托的优化版本:

              using System.Windows.Forms;
              using System.Collections.Generic;
              using System;
              
              namespace MyTool
              {
                  public class DelayedDelegate
                  {
                     static private DelayedDelegate _instance = null;
              
                      private Timer _runDelegates = null;
              
                      private Dictionary<MethodInvoker, DateTime> _delayedDelegates = new Dictionary<MethodInvoker, DateTime>();
              
                      public DelayedDelegate()
                      {
                      }
              
                      static private DelayedDelegate Instance
                      {
                          get
                          {
                              if (_instance == null)
                              {
                                  _instance = new DelayedDelegate();
                              }
              
                              return _instance;
                          }
                      }
              
                      public static void Add(MethodInvoker pMethod, int pDelay)
                      {
                          Instance.AddNewDelegate(pMethod, pDelay * 1000);
                      }
              
                      public static void AddMilliseconds(MethodInvoker pMethod, int pDelay)
                      {
                          Instance.AddNewDelegate(pMethod, pDelay);
                      }
              
                      private void AddNewDelegate(MethodInvoker pMethod, int pDelay)
                      {
                          if (_runDelegates == null)
                          {
                              _runDelegates = new Timer();
                              _runDelegates.Tick += RunDelegates;
                          }
                          else
                          {
                              _runDelegates.Stop();
                          }
              
                          _delayedDelegates.Add(pMethod, DateTime.Now + TimeSpan.FromMilliseconds(pDelay));
              
                          StartTimer();
                      }
              
                      private void StartTimer()
                      {
                          if (_delayedDelegates.Count > 0)
                          {
                              int delay = FindSoonestDelay();
                              if (delay == 0)
                              {
                                  RunDelegates();
                              }
                              else
                              {
                                  _runDelegates.Interval = delay;
                                  _runDelegates.Start();
                              }
                          }
                      }
              
                      private int FindSoonestDelay()
                      {
                          int soonest = int.MaxValue;
                          TimeSpan remaining;
              
                          foreach (MethodInvoker invoker in _delayedDelegates.Keys)
                          {
                              remaining = _delayedDelegates[invoker] - DateTime.Now;
                              soonest = Math.Max(0, Math.Min(soonest, (int)remaining.TotalMilliseconds));
                          }
              
                          return soonest;
                      }
              
                      private void RunDelegates(object pSender = null, EventArgs pE = null)
                      {
                          try
                          {
                              _runDelegates.Stop();
              
                              List<MethodInvoker> removeDelegates = new List<MethodInvoker>();
              
                              foreach (MethodInvoker method in _delayedDelegates.Keys)
                              {
                                  if (DateTime.Now >= _delayedDelegates[method])
                                  {
                                      method();
              
                                      removeDelegates.Add(method);
                                  }
                              }
              
                              foreach (MethodInvoker method in removeDelegates)
                              {
                                  _delayedDelegates.Remove(method);
                              }
                          }
                          catch (Exception ex)
                          {
                          }
                          finally
                          {
                              StartTimer();
                          }
                      }
                  }
              }
              

              使用代表的唯一键可以稍微改进该类。 因为如果您在第一次触发之前第二次添加相同的委托,您可能会遇到字典问题。

              【讨论】:

                【解决方案12】:
                private static volatile List<System.Threading.Timer> _timers = new List<System.Threading.Timer>();
                        private static object lockobj = new object();
                        public static void SetTimeout(Action action, int delayInMilliseconds)
                        {
                            System.Threading.Timer timer = null;
                            var cb = new System.Threading.TimerCallback((state) =>
                            {
                                lock (lockobj)
                                    _timers.Remove(timer);
                                timer.Dispose();
                                action()
                            });
                            lock (lockobj)
                                _timers.Add(timer = new System.Threading.Timer(cb, null, delayInMilliseconds, System.Threading.Timeout.Infinite));
                }
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-03-22
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多