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.
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.
精彩评论