So when was doing batch update the screen freezes for way too much time. I found out the problem was the cascading effect of recalculating all the dependent fields every time I add a ne line in the collection, thanks to the new Visual Studio 2010 performance analysis tools.
In this scenario, I’m only interested to get the totals right when I do the last insert, all other intermediate values are useless. I didn’t want to play with the data binding itself. I could have unbound the total calculation and have it rebinded at the end but I didn’t. Instead I chose to delay the calculation with some kind of sliding expiration timer. Such a timer, set with a timeout of 10 ms, would be reset every time a new add would be made, so if I add a bunch of line in batch odds that the delay between two line add be less than 10 ms is good. In fact it is so good that because it’s almost always true the calculation happens only once at the end.
So there is this magic DelayRun class with its DelayController:
using System; using System.Collections.Generic; using System.Threading; namespace Infrastructure { public interface IDelayRun { void Reset(); void Start(int ms); event EventHandler<DelayRunEventArgs> EventCompleted; } public class DelayRun<T> : IDisposable, IDelayRun { private static readonly ManualResetEvent _resetEvent = new ManualResetEvent(true); private readonly Action<T> _action; private readonly T _target; private int _delay; private volatile Timer _timer; private DelayRun(T target, Action<T> action) { _action = action; _target = target; } #region IDelayRun Members public void Start(int ms) { if (ms == 0) throw new ArgumentOutOfRangeException("ms", ms, "ms should be grater than 0."); if (_delay != 0) throw new InvalidOperationException("Can't start, already started."); _delay = ms; _timer = new Timer(Execute, this, _delay, Timeout.Infinite); } public void Reset() { lock (_timer) { if (_timer == null) throw new InvalidOperationException("Can't reset the timer, already completed."); _timer.Change(_delay, Timeout.Infinite); } } #endregion public static DelayRun<T> CreateNew(T target, Action<T> action) { return new DelayRun<T>(target, action); } public static DelayRun<T> StartNew(T target, Action<T> action, int ms) { DelayRun<T> delayRun = CreateNew(target, action); delayRun.Start(ms); return delayRun; } public void Execute(object state) { _resetEvent.WaitOne(); lock (_timer) { _timer.Change(Timeout.Infinite, Timeout.Infinite); } _action.Invoke(_target); _resetEvent.Set(); } #region Event Handling public event EventHandler<DelayRunEventArgs> EventCompleted; public void InvokeEventCompleted(DelayRunEventArgs e) { EventHandler<DelayRunEventArgs> handler = EventCompleted; if (handler != null) handler(this, e); } #endregion #region IDisposable public void Dispose() { lock (_timer) { if (_timer != null) _timer.Dispose(); } } #endregion } public class DelayRunEventArgs : EventArgs { public DelayRunEventArgs(IDelayRun delayAction) { DelayAction = delayAction; } public IDelayRun DelayAction { get; set; } } public static class DelayController { private static readonly IDictionary<string, IDelayRun> _actions = new Dictionary<string, IDelayRun>(); public static void Add<T>(string key, T target, Action<T> action, int ms) { if (_actions.ContainsKey(key)) _actions[key].Reset(); else { DelayRun<T> delayRun = DelayRun<T>.CreateNew(target, _ => {}); _actions[key] = delayRun; string localKey = key; delayRun.EventCompleted += (sender, args) => { _actions.Remove(localKey); action.BeginInvoke(target, ar => ar.AsyncWaitHandle.WaitOne(), delayRun); }; delayRun.Start(ms); } } } }To use this class all you need to do is call the DelayController. The DelayController will handle the creation and the reset process of the DelayRun class. It can handle many delay class at once if they have different key value. Each time the controller Add method is called it either create a new instance of a DelayRun Class of reset the running one.
Here is how you can call it:
DelayController.Add("ValuesOnCollectionChanged", this, m => Recalc(m), 10);This call will delay the Recalc method call for 10 ms. In the mean time if your program add other values to recalc you can recall this line and the delay will be reset for another 10 ms and so on.
Tell me if this can be helpful in your own projects.