开发者

Call Method B if method A is not called for more than N seconds

I'm using following code to call Method B after N seconds method A is called. If method A is called again within the N seconds timeout, i have to reset the time counting back to N seconds.

I cannot reference System.Windows.Form in my project, so I cannot use System.Windows.Form.Timer.

The method B must be called in the same thread A is called.

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)
                            {
                 开发者_StackOverflow               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

}

Actually the code work, but i think it's a bit confounding. Do you know if there is a best practices to achieve the same result?


It's a shame you're stuck on .NET 2.0, because Rx extensions has a Throttle method that achieves this effect quite elegantly.

Sadly Rx requires at least .NET 3.5 SP1.

Oh well! You can always use a System.Threading.Timer to get this done instead. Synchronization can be provided by leveraging the current SynchronizationContext (this is what BackgroundWorker does).

Here's a sketch of a LaggedMethodPair class to illustrate this approach. The class takes three inputs in its constructor: an Action to be performed on-demand, another Action to serve as the callback that will be invoked when a given timeout has elapsed, and, of course, the timeout itself:

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);
    }
}

I wrote a sample Windows Forms app to show this class in action. The form contains a LaggedMethodPair member with a timeout of 2000 ms. Its primaryAction adds an item to a list view. Its laggedCallback adds a highlighted item to the list view.

You can see that the code runs as expected.

Call Method B if method A is not called for more than N seconds


I would encapsulate this functionality into a timer class with events that other classes can subscribe to (for example a timer.tick event).


I am trying to use AutoResetEvent, because it is capable to wait for a signal. I use it to have worker waited for the signal from A(), and if it has been too long B() will be called.

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();
        }
    }
}

I have tested my class roughly and it is working fine so far. Note that B will be called from worker thread, so you have to do the synchronization in B() if needed.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜