/* Copyright (c) Cloud Software Group, Inc. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System; using System.Threading; using System.Diagnostics; namespace XenAdmin { /// /// A class to throttle the updates to a particular class/component. /// /// The purpose of the class is best described by example: /// It is used by to control the refreshes to the TreeView. /// The refresh of the tree is slow and it is called all over the place. /// This class ensures there is always a small break between refreshes /// - the length of the break is calculated by . /// /// This class also allows consumers to specify a maximum time between updates. /// internal partial class UpdateManager : IDisposable { /// /// Used to ensure the maximum time between updates. (ms) /// private readonly int _longInterval; private bool _disposed; private Timer _shortIntervalTimer; private Timer _longIntervalTimer; private readonly DelayCalculator _delayCalculator = new DelayCalculator(); private readonly object _lock = new object(); /// /// A Boolean indicating whether an update is currently taking place. /// The _lock variable should be used when reading and writing to this variable. /// private bool _doingUpdate; /// /// A Boolean indicating whether an update request was received while the current update is taking place. /// The _lock variable should be used when reading and writing to this variable. /// private bool _anotherUpdateRequestOccurredDuringUpdate; /// /// Initializes a new instance of the class. /// /// The maximum time between updates (ms). Specify 0 for no maximum value. public UpdateManager(int longInterval) { _longInterval = longInterval; } /// /// Initializes a new instance of the class. There is no maximum time between updates specified. /// public UpdateManager() : this(0) { } /// /// Requests an update. The event occurs when an update should take place. /// public void RequestUpdate() { _delayCalculator.RegisterLatestUpdateRequest(); RequestUpdate(null); } private void RequestUpdate(object state) { if (TryBeginUpdate()) { // ensure update occurs on a new thread. This frees the calling thread up to continue. ThreadPool.QueueUserWorkItem(DoUpdate); } } private void DoUpdate(object state) { try { // stop watch to time how long an update takes. Stopwatch sw = Stopwatch.StartNew(); OnUpdate(); _delayCalculator.RegisterLatestUpdate(sw.ElapsedMilliseconds); } finally { // allow another request only after _delayCalculator.GetDelay() has elapsed. _shortIntervalTimer = new Timer(EndUpdate, null, _delayCalculator.GetDelay(), Timeout.Infinite); } } /// /// Tries to begin an Update. /// /// Returns false if an update is already underway. private bool TryBeginUpdate() { lock (_lock) { if (!_doingUpdate && !_disposed) { // now do an update so reset this variable. _anotherUpdateRequestOccurredDuringUpdate = false; DisposeTimer(_longIntervalTimer); _doingUpdate = true; return true; } // an update is taking place and another request has taken place. _anotherUpdateRequestOccurredDuringUpdate = true; return false; } } private void EndUpdate(object state) { lock (_lock) { _doingUpdate = false; DisposeTimer(_shortIntervalTimer); if (!_disposed) { if (_longInterval > 0) { _longIntervalTimer = new Timer(RequestUpdate, null, _longInterval, _longInterval); } if (_anotherUpdateRequestOccurredDuringUpdate) { // another update was requested during this update RequestUpdate(); } } } } /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnUpdate() { var handler = Update; if (handler != null) { handler(); } } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (disposing) { lock (_lock) { if (!_disposed) { _disposed = true; DisposeTimer(_longIntervalTimer); DisposeTimer(_shortIntervalTimer); } } GC.SuppressFinalize(this); } } private static void DisposeTimer(Timer timer) { if (timer != null) { timer.Dispose(); } } /// /// Occurs when an update should take place. /// public event Action Update; #region IDisposable Members /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); } #endregion } }