【问题标题】:Call Method B if method A is not called for more than N seconds如果方法 A 没有被调用超过 N 秒,则调用方法 B
【发布时间】:2010-09-10 11:49:10
【问题描述】:

我正在使用以下代码在调用方法 A 的 N 秒后调用方法 B。如果方法 A 在 N 秒超时内再次调用,我必须将时间重置为 N 秒。

我不能在我的项目中引用 System.Windows.Form,所以我不能使用 System.Windows.Form.Timer。

方法 B 必须在调用 A 的同一个线程中调用。

private void InitTimer()
{
    timer = new BackgroundWorker();
    timer.WorkerSupportsCancellation = true;
    timer.WorkerReportsProgress = true;
    timer.DoWork += delegate(object sender, DoWorkEventArgs e)
                    {
                        var st = DateTime.Now;
                        while (DateTime.Now.Subtract(st).TotalSeconds < 10)
                        {
                            if (timer.CancellationPending)
                            {
                                e.Cancel = true;
                                return;
                            }
                        }

                    };
    timer.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs e)
                    {
                        if (!e.Cancelled)
                        {    
                            MethodB();
                        }
                        else
                        {
                            timer.RunWorkerAsync();
                        }
                    };
}



public void MethodA()
{
     if (timer.IsBusy)
         timer.CancelAsync();
     else
         timer.RunWorkerAsync();

}

public void MethodB()
{
     //do some stuff

}

实际上代码可以工作,但我认为这有点令人困惑。你知道是否有达到相同结果的最佳实践吗?

【问题讨论】:

    标签: c# .net multithreading c#-3.0


    【解决方案1】:

    很遗憾您被困在 .NET 2.0 上,因为 Rx extensions 有一个 Throttle 方法可以非常优雅地实现此效果。

    遗憾的是,Rx 至少需要 .NET 3.5 SP1。

    哦,好吧!您始终可以使用System.Threading.Timer 来完成此操作。可以通过利用当前的SynchronizationContext 来提供同步(这就是BackgroundWorker 所做的)。

    这里有一个LaggedMethodPair 类的草图来说明这种方法。该类在其构造函数中接受三个输入:一个 Action 按需执行,另一个 Action 用作在给定超时过去时将调用的回调,当然还有超时本身:

    public sealed class LaggedMethodPair
    {
        private SynchronizationContext _context;
        private Timer _timer;
    
        private Action _primaryAction;
        private Action _laggedCallback;
        private int _millisecondsLag;
    
        public LaggedMethodPair(Action primaryAction,
                                Action laggedCallback,
                                int millisecondsLag)
        {
            if (millisecondsLag < 0)
            {
                throw new ArgumentOutOfRangeException("Lag cannot be negative.");
            }
    
            // Do nothing by default.
            _primaryAction = primaryAction ?? new Action(() => { });
    
            // Do nothing by default.
            _laggedCallback = laggedCallback ?? new Action(() => { });
    
            _millisecondsLag = millisecondsLag;
    
            _timer = new Timer(state => RunTimer());
        }
    
        public void Invoke()
        {
            // Technically there is a race condition here.
            // It could be addressed, but in practice it will
            // generally not matter as long as Invoke is always
            // being called from the same SynchronizationContext.
            if (SynchronizationContext.Current == null)
            {
                SynchronizationContext.SetSynchronizationContext(
                    new SynchronizationContext()
                );
            }
    
            _context = SynchronizationContext.Current;
    
            ResetTimer();
    
            _primaryAction();
        }
    
        void ResetTimer()
        {
            _timer.Change(_millisecondsLag, Timeout.Infinite);
        }
    
        void RunTimer()
        {
            _context.Post(state => _laggedCallback(), null);
        }
    }
    

    我编写了一个示例 Windows 窗体应用程序来展示这个类的实际应用。该表单包含一个LaggedMethodPair 成员,超时时间为 2000 毫秒。它的primaryAction 将一个项目添加到列表视图中。它的laggedCallback 将突出显示的项目添加到列表视图中。

    可以看到代码按预期运行。

    【讨论】:

    • 好的,但是这样TimerCallback不是在自己的线程中执行的吗?我必须在调用 MethodA() 的同一线程中调用 MethodB()
    • 是的,你是对的:MethodB 将在线程池线程上调用。通常,如果它需要从与 MethodA 相同的线程执行,您可以 (a) 使用您自己的同步机制(例如,在 WinForms 应用程序中,您可以使用 Control.BeginInvoke);或者 (b) 利用一些内置的同步技术,例如附加到 ISynchronizeInvoke 对象(这是 System.Timers.Timer 类所做的)或 SynchronizationContext(这是 BackgroundWorker 类所做的) )。我将更新答案以提供示例。
    【解决方案2】:

    我会将此功能封装到一个计时器类中,其中包含其他类可以订阅的事件(例如 timer.tick 事件)。

    【讨论】:

    • 我想到了。通过这种方式,我可以阐明 timer.CancelAsync() 和 timer.RunWorkerAsync() 的意图。您认为我的代码中有一些禁忌症或副作用吗?
    【解决方案3】:

    我正在尝试使用AutoResetEvent,因为它能够等待信号。我用它让worker等待来自A()的信号,如果时间太长,会调用B()。

    class Caller
    {
        AutoResetEvent ev = new AutoResetEvent(false);
        public void A()
        {
            ev.Set();
            // do your stuff
            Console.Out.WriteLine("A---");
        }
        void B()
        {
            Console.Out.WriteLine("B---");
        }
    
        public void Start()
        {
            var checker = new BackgroundWorker();
            checker.DoWork += new DoWorkEventHandler(checker_DoWork);
            checker.RunWorkerAsync();
        }
    
        void checker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            while (!worker.CancellationPending)
            {
                bool called = ev.WaitOne(TimeSpan.FromSeconds(3));
                if (!called) B();
            }
        }
    }
    

    我已经粗略地测试了我的课程,到目前为止它运行良好。请注意,B 将从工作线程中调用,因此如果需要,您必须在 B() 中进行同步。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-12
      • 2016-07-06
      • 2012-03-13
      • 2019-05-14
      • 1970-01-01
      • 2014-07-10
      • 2014-06-02
      相关资源
      最近更新 更多