This is an iteration of my previous question: Concurrent Task Waiter
Summary from before:
I have some code designed to simplify managing multiple asynchronous operations. The code creates callback actions that, when executed by the asynchronous operation, track which asynchronous methods have completed. When all have completed, the class executes a specially-assigned method.
The purpose of this is to wait for multiple resources to load, without chaining them, in projects that do not have access to async / await.
One note on style: The egregious use of regions and over-commenting is company style, I'm afraid, and I can't change it. Otherwise I'm looking for advice on style, efficiency, security, and particularly type safety.
I've followed robertfriberg's advice and altered the order of execution of the Finished function, as well as introduced a guarantee that when a callback is executed, it is removed from the list and cannot be executed twice.
/// <summary> /// Waits for multiple concurrent operations to finish before firing a callback /// </summary> public class TtConcurrentSynchronizer { #region Properties private readonly List<Callback> callbacks = new List<Callback>(); /// <summary> /// The callbacks that must all be executed before the finished function is fired. /// </summary> public IEnumerable<Action> Callbacks { get { return callbacks.Select(x => CreateCallbackAction(x)); } } #endregion Properties #region Private Fields private Action finishedFunction; #endregion Private Fields #region Constructors /// <summary> /// Creates a new instance of the concurrent synchronizer /// </summary> /// <param name="finishedFunction"> /// Method that is executed when all assigned asynchronous methods have completed. /// </param> public TtConcurrentSynchronizer(Action finishedFunction) { this.finishedFunction = finishedFunction; } #endregion Constructors #region Public Methods /// <summary> /// Create a callback function to send to the asynchronous method /// </summary> /// <returns> A callback action that invokes an internal checking function </returns> public Action CreateCallBack() { var callback = new Callback(); var callbackAction = CreateCallbackAction(callback); callbacks.Add(callback); return callbackAction; } /// <summary> /// Create a callback function to send to the asynchronous method, combined with a provided function /// </summary> /// <param name="additionalFunction"> /// An additional function to call on the method's completion /// </param> /// <returns> /// A callback action that invokes an internal checking function as well as an additional function /// </returns> public Action CreateCallBack(Action additionalFunction) { var callback = new Callback(additionalFunction); var callbackAction = CreateCallbackAction(callback); callbacks.Add(callback); return callbackAction; } /// <summary> /// Create an action that removes a callback from the callbacks array and fires its callback /// method if it hasn't already been removed. /// </summary> /// <param name="callback"> The callback to create an action for. </param> private Action CreateCallbackAction(Callback callback) { return () => { if (callbacks.Contains(callback)) { if (callback.CallbackMethod != null) { callback.CallbackMethod(); } callbacks.Remove(callback); if (!callbacks.Any()) { finishedFunction(); } } }; } #endregion Public Methods #region Private Classes /// <summary> /// Represents a concurrency callback function. /// </summary> private class Callback { #region Private Fields private readonly Guid id = Guid.NewGuid(); #endregion Private Fields #region Public Properties private Action callbackMethod = null; /// <summary> /// Gets the method executed when the callback is called. /// </summary> public Action CallbackMethod { get { return callbackMethod; } } /// <summary> /// Gets a unique identifier for this callback. /// </summary> public Guid Id { get { return id; } } #endregion Public Properties #region Public Constructors /// <summary> /// Initializes a new Callback instance. Use this constructor when you want additional /// functionality on callback. /// </summary> public Callback(Action callbackMethod) { this.callbackMethod = callbackMethod; } /// <summary> /// Initializes a new Callback instance. Use this constructor when you don't want any /// additional functionality on callback. Equivalent to new Callback(null); /// </summary> public Callback() { } #endregion Public Constructors } #endregion Private Classes } Once again, typical usage is as follows:
Action finishedFunction = LongProcessFinished; var concurrentSynchronizer = new ConcurrentSynchronizer(finishedFunction); BackgroundWorker longProcess1 = new BackgroundWorker(); var longProcess1Callback = concurrentSynchronizer.CreateCallback(); longProcess1.DoWork += (o,e) => { DoLongProcess(); }; longProcess1.RunWorkerCompleted += (o, e) => longProcess1Callback(); //With an additional callback Action longProcess2AdditionalCallback = AdditionalCallback; var longProcess2 = new BackgroundWorker(); Action longProcess2Callback = concurrentSynchronizer.CreateCallback(longProcess2AdditionalCallback); longProcess2.DoWork += (o,e) => { DoLongProcess2(); }; longProcess2.RunWorkerCompleted += (o, e) => longProcess2Callback(); longProcess1.RunWorkerAsync(); longProcess2.RunWorkerAsync(); I was also asked why I did not use Tasks or WaitHandle.WaitAll(). The reasons are:
- I aim to target .NET 3.5, so Tasks are out of the equation.
- I did not like the intrusive syntax of WaitHandles, with my system I can use existing asynchronous operations without modifying them in any way, including WCF calls and the like.