//////////////////////////////////////////////////////////////////////////////// // WorkerPool - A wrapper around the BackgroundWorker class. // // Copyright (c) 2008 Jose Simas (josesimas at gepsoft.com). All Rights Reserved. // // Permission to use, copy, modify, and distribute this software and its // documentation for any purpose, without fee, and without a written // agreement, is hereby granted, provided that the above copyright notice, // this paragraph and the following two paragraphs appear in all copies, // modifications, and distributions. // // IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, // INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST // PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, // EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A // PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF // ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". THE AUTHOR HAS NO OBLIGATION // TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. // // V0.9 23/01/2008 - First release for comments //////////////////////////////////////////////////////////////////////////////// namespace Gepsoft.WorkerPool { using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; /// /// Maintains a list of workers and manages their lifetimes. /// /// Can be called from any thread. /// public static class WorkerPool { private static readonly IList _workers = new List(); /// /// Returns the next free worker or a new worker if all the /// existing ones are busy. /// public static Worker Next { get { lock (typeof(WorkerPool)) { var nextFreeWorker = from w in _workers where ! w.IsBusy select w; if (nextFreeWorker.Count() == 0) { _workers.Add(new Worker()); return _workers[_workers.Count - 1]; } var worker = nextFreeWorker.First(); ((IDisposable)worker).Dispose(); return worker; } } } /// /// The number of active workers in the pool. /// public static int Count { get { lock (typeof(WorkerPool)) return _workers.Count; } } /// /// It is true if any of the workers in the pool is busy. /// public static bool IsBusy { get { lock (typeof (WorkerPool)) return (from w in _workers where w.IsBusy select w).Count() > 0; } } /// /// Disposes all workers and clears the pool. Usually you are not required /// to call this method but it may be useful for debugging or when trying /// to recover from a general exception. /// public static void Clear() { lock (typeof(WorkerPool)) { foreach (IDisposable w in _workers) w.Dispose(); _workers.Clear(); } } /// /// Wrapper for the BackgroundWorker class. /// /// The difference is that this class supports a fluent interface, is /// managed by the WorkerPool class and supports passing multiple /// parameters to the background thread. /// public class Worker : IDisposable { //No direct instantiation allowed. Get instances through the WorkerPool internal Worker() { } /// /// This delegate simplifies calls when there is no need to pass arguments and the sender. /// public delegate void NoArgsEventHandler(); /// /// This delegate simplifies calls when there is no need to pass the sender. /// public delegate void ArgsOnlyDoWorkEventHandler(DoWorkEventArgs e); /// /// This delegate simplifies calls when there is no need to pass the sender. /// public delegate void ArgsOnlyOnFinishedEventHandler(RunWorkerCompletedEventArgs e); //private data private BackgroundWorker _worker; readonly IList _arguments = new List(); private object _argument; private bool _inSetupMode; private bool _hasWork; /// /// A worker is busy while it is being /// setup and while it is doing the work. /// public bool IsBusy { get { if (_worker == null) return false; return _inSetupMode || _worker.IsBusy; } } /// /// The list of arguments to be passed to the background thread. /// public IList Arguments { get { return _arguments; } } /// /// The argument to be passed to the background thread. /// If there are more than one argument then this is null /// and all the arguments are in the Arguments property. /// public object Argument { get { return _argument; } } /// /// Optional. Add arguments to be passed to the background thread. /// /// If there is only one argument then it is passed as it is, when there /// are several arguments then they are packed into a list and that list /// is passed to the background thread. /// /// Any number of arguments /// Self public Worker AddArguments(params object[] arguments) { _inSetupMode = true; _arguments.Clear(); _argument = null; if (arguments.Length == 1) _argument = arguments[0]; else foreach (var obj in arguments) _arguments.Add(obj); return this; } /// /// Required. /// /// Pass the delegate to be processed in the /// background thread to the method. /// /// A delegate of the type used by the BackgroundWorker. /// Self public Worker DoWork(DoWorkEventHandler work) { _inSetupMode = true; if (_worker != null) _worker.Dispose(); _worker = new BackgroundWorker(); _worker.DoWork += work; _hasWork = true; return this; } /// /// Required. /// /// Pass the delegate to be processed in the /// background thread to the method. /// /// This overload simplifies calls when no state transfer is /// required. /// /// /// public Worker DoWork(NoArgsEventHandler completed) { return DoWork((sender, e) => completed.Invoke()); } /// /// Required. /// /// Pass the delegate to be processed in the /// background thread to the method. /// /// This overload simplifies calls when no sender is /// required. /// /// /// public Worker DoWork(ArgsOnlyDoWorkEventHandler completed) { return DoWork((sender, e) => completed.Invoke(e)); } /// /// Optional. /// /// Pass a delegate to be called when reporting /// progress in the background thread. The /// code in that delegate will run in the caller's thread. /// /// A delegate of the type used by the BackgroundWorker. /// public Worker OnProgressChanged(ProgressChangedEventHandler progress) { _worker.ProgressChanged += progress; return this; } /// /// Required. /// /// Pass a delegate to be called when the work is finished. The /// code in that delegate will run in the caller's thread. /// /// /// public Worker WhenFinished(RunWorkerCompletedEventHandler completed) { if (!_hasWork) throw new WorkerNotReadyException(); _worker.RunWorkerCompleted += completed; Finish(); return this; } /// /// Required. /// /// Pass a delegate to be called when the work is finished. The /// code in that delegate will run in the caller's thread. /// /// This overload simplifies calls when no state transfer is /// required. /// /// /// public Worker WhenFinished(NoArgsEventHandler completed) { return WhenFinished((sender, e) => completed.Invoke()); } /// /// Required. /// /// Pass a delegate to be called when the work is finished. The /// code in that delegate will run in the caller's thread. /// /// This overload simplifies calls when no state transfer is /// required. /// /// /// public Worker WhenFinished(ArgsOnlyOnFinishedEventHandler completed) { return WhenFinished((sender, e) => completed.Invoke(e)); } /// /// Required. /// /// Pass a delegate to be called when the work is finished. The /// code in that delegate will run in the caller's thread. /// /// This overload simplifies calls when no state transfer is /// required. /// /// public Worker WhenFinished() { return WhenFinished((sender, e) => { }); } /// /// Do the work. Can also be called for Fire-And-Forget scenarios. /// public void Finish() { if (_argument != null) _worker.RunWorkerAsync(_argument); else if (_arguments.Count > 0) _worker.RunWorkerAsync(_arguments); else _worker.RunWorkerAsync(); _inSetupMode = false; } /// /// To be called from within the delegate passed to DoWork. /// Used to report progress changes to the callers. /// /// Value that will passed to the caller. public void ReportProgress(int percentProgress) { _worker.ReportProgress(percentProgress); } /// /// To be called from within the delegate passed to DoWork. /// Used to report progress changes to the callers. /// /// Value that will passed to the caller. /// Any object that will passed to the caller. public void ReportProgress(int percentProgress, object userState) { _worker.ReportProgress(percentProgress, userState); } /// /// Cancel the background thread. /// public void Cancel() { _worker.CancelAsync(); } #region Implementation of IDisposable void IDisposable.Dispose() { if (_worker != null) _worker.Dispose(); _arguments.Clear(); _argument = null; _hasWork = false; _inSetupMode = false; _worker = null; } #endregion #region Exceptions public class WorkerNotReadyException : Exception { public WorkerNotReadyException() : base("Please add some work to DoWork before calling Start") { } } #endregion } } /// /// Extension methods for the Workers /// public static class Extensions { /// /// Simplify the cast of e.Argument when there is more than one argument. /// /// /// public static IList ToArgumentList(this object arguments) { return (IList)arguments; } } }