Click here to Skip to main content
15,891,764 members
Articles / Programming Languages / Visual Basic

Automatic Implementation of the Event-Based Asynchronous Pattern

Rate me:
Please Sign up or sign in to vote.
4.78/5 (32 votes)
26 Nov 2008CPOL20 min read 63.4K   912   101  
Implement the event-based asynchronous pattern automatically with this code generator
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting.Messaging;
using System.ComponentModel;
using System.Threading;
using System.Reflection;
using System.Diagnostics;
using System.Runtime.Remoting.Lifetime;

namespace AsyncGen
{
    /// <summary>
    /// A base class for objects that track every invocation of a given operation.
    /// </summary>
    /// <typeparam name="TServer">The server class or interface.</typeparam>
    /// <typeparam name="TClient">The client class that contains this tracker.</typeparam>
    /// <typeparam name="TDelegate">A delegate type that represents the signature of the basic (synchronous) method.</typeparam>
    /// <typeparam name="TOutput">The output of the operation.  If the operation has multiple outputs, a <c>struct</c> that encapsulates all of them.</typeparam>
    public abstract class OperationTracker<TServer, TClient, TDelegate, TOutput> : MarshalByRefObject, IDisposable where TClient : ClientBase<TServer>
    {
        #region IDisposable Members

        /// <summary>
        /// Tears down the object in a safe manner.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        #region MarshalByRefObject Overrides

        /// <summary>
        /// Called by the Remoting infrastructure to set the lifetime policy for this object.
        /// </summary>
        /// <remarks>
        /// This method registers the server as a sponsor for the tracker.
        /// </remarks>
        /// <returns>The lease for this object.</returns>
        public override object InitializeLifetimeService()
        {
            ILease lease = (ILease)base.InitializeLifetimeService();
            if (lease.CurrentState == LeaseState.Initial)
            {
                ISponsor sponsor = server as ISponsor;
                if (sponsor != null)
                {
                    lease.Register(sponsor);
                }
            }
            return lease;
        }

        #endregion

        #region Construction

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="server">The object that implements the basic, synchronous, operation.</param>
        /// <param name="client">The proxy that contains this tracker.</param>
        public OperationTracker(TServer server, TClient client)
        {
            this.server = server;
            this.client = client;
        }

        #endregion

        #region Destruction

        /// <summary>
        /// Finalizes the object.
        /// </summary>
        ~OperationTracker()
        {
            Dispose(false);
        }

        #endregion

        #region Public Events

        /// <summary>
        /// Indicates to the client that an operation has completed.
        /// </summary>
        public event AsyncCompletedEventHandler<TOutput> OperationCompleted;

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates a new <see cref="OperationState"/> and adds it to the list.
        /// </summary>
        public void CreateOperation()
        {
            CreateOperation(dummyTaskID);
        }

        /// <summary>
        /// Creates a new <see cref="OperationState"/> and adds it to the list.
        /// </summary>
        /// <param name="userState">An object that uniquely identifies a specific invocation of the operation.</param>
        public void CreateOperation(object userState)
        {
            lock (opStateForTaskID)
            {
                opStateForTaskID.Add(userState, new OperationState(userState));
            }
        }

        /// <summary>
        /// Signals to the server that the operation should be aborted.
        /// </summary>
        /// <returns><c>true</c> if successful, <c>false</c> otherwise.</returns>
        public bool TryCancelOperation()
        {
            return TryCancelOperation(dummyTaskID);
        }

        /// <summary>
        /// Signals to the server that an operation should be aborted.
        /// </summary>
        /// <param name="userState">An object that uniquely identifies a specific invocation of the operation.</param>
        /// <returns><c>true</c> if successful, <c>false</c> otherwise.</returns>
        public bool TryCancelOperation(object userState)
        {
            lock (opStateForTaskID)
            {
                OperationState opState = null;
                if (opStateForTaskID.TryGetValue(userState, out opState))
                {
                    return opState.Cancelled = true;
                }
                else
                {
                    return false;
                }
            }
        }

        /// <summary>
        /// Returns a value indicating whether the client requested the server to abort the operation.
        /// </summary>
        /// <returns><c>true</c> if cancellation is requested, <c>false</c> otherwise.</returns>
        public bool IsOperationCancelled()
        {
            return IsOperationCancelled(dummyTaskID);
        }

        /// <summary>
        /// Returns a value indicating whether the client requested the server to abort the operation.
        /// </summary>
        /// <param name="userState">An object that uniquely identifies a specific invocation of the operation.</param>
        /// <returns><c>true</c> if cancellation is requested, <c>false</c> otherwise.</returns>
        public bool IsOperationCancelled(object userState)
        {
            lock (opStateForTaskID)
            {
                OperationState opState = null;
                return !opStateForTaskID.TryGetValue(userState, out opState) || opState.Cancelled;
            }
        }

        /// <summary>
        /// Completes the operation without raising the completion event.
        /// </summary>
        /// <remarks>This method is used to implement the synchronous version of the operation.</remarks>
        public void CompleteOperation()
        {
            CompleteOperation(dummyTaskID);
        }

        /// <summary>
        /// Completes an operation without raising the completion event.
        /// </summary>
        /// <remarks>This method is used to implement the synchronous version of the operation.</remarks>
        /// <param name="userState">An object that uniquely identifies a specific invocation of the operation.</param>
        public void CompleteOperation(object userState)
        {
            lock (opStateForTaskID)
            {
                OperationState opState = null;
                if (opStateForTaskID.TryGetValue(userState, out opState))
                {
                    opStateForTaskID.Remove(userState);
                    opState.Operation.OperationCompleted();
                }
                else
                {
                    throw new InvalidOperationException(string.Format("No operation has task ID {0}.", userState));
                }
            }
        }

        /// <summary>
        /// Completes an operation and raises the completion event.
        /// </summary>
        /// <param name="iar">The <see cref="IAsyncResult"/> returned by <c>BeginInvoke</c>.</param>
        public void PostOperationCompleted(IAsyncResult iar)
        {
            lock (opStateForTaskID)
            {
                OperationState opState = null;
                object key = iar.AsyncState == null ? dummyTaskID : iar.AsyncState;
                if (!opStateForTaskID.TryGetValue(key, out opState))
                {
                    throw new InvalidOperationException(string.Format("No operation found with task ID {0}.", iar.AsyncState));
                }
                opStateForTaskID.Remove(key);
                TOutput output = default(TOutput);
                Exception error = null;
                AsyncResult ar = iar as AsyncResult;
                TDelegate d = (ar == null ? default(TDelegate) : (TDelegate)ar.AsyncDelegate);
                try
                {
                    CallEndInvoke(d, iar, out output);
                }
                catch (Exception ex)
                {
                    error = ex;
                }
                AsyncCompletedEventArgs<TOutput> args = new AsyncCompletedEventArgs<TOutput>(
                    output,
                    error,
                    opState.Cancelled,
                    opState.Operation.UserSuppliedState);
                opState.Operation.PostOperationCompleted(OnOperationCompleted, args);
            }
        }

        /// <summary>
        /// Raises the progress event synchronously on the proper thread.
        /// </summary>
        /// <param name="d">A <see cref="SendOrPostCallback"/> bound to one of the <c>OnProgressChanged</c> methods in this class.</param>
        /// <param name="progressPercentage">An <see cref="Int32"/> between 0 and 100 that indicates the progress of the operation.</param>
        public void SendProgress(SendOrPostCallback d, int progressPercentage)
        {
            SendProgress(d, progressPercentage, dummyTaskID);
        }

        /// <summary>
        /// Raises the progress event synchronously on the proper thread.
        /// </summary>
        /// <param name="d">A <see cref="SendOrPostCallback"/> bound to one of the <c>OnProgressChanged</c> methods in this class.</param>
        /// <param name="progressPercentage">An <see cref="Int32"/> between 0 and 100 that indicates the progress of the operation.</param>
        /// <param name="userState">An object that uniquely identifies a specific invocation of the operation.</param>
        public void SendProgress(SendOrPostCallback d, int progressPercentage, object userState)
        {
            OperationState opState = GetOperationState(userState);
            opState.Operation.SynchronizationContext.Send(d, new ProgressChangedEventArgs(progressPercentage, userState));
        }

        /// <summary>
        /// Raises the progress event synchronously on the proper thread.
        /// </summary>
        /// <typeparam name="TResults">The type of the incremental results.</typeparam>
        /// <param name="d">A <see cref="SendOrPostCallback"/> bound to one of the <c>OnProgressChanged</c> methods in this class.</param>
        /// <param name="progressPercentage">An <see cref="Int32"/> between 0 and 100 that indicates the progress of the operation.</param>
        /// <param name="results">The incremental results of the operation, packed into a single <c>struct</c> if there are more than one.</param>
        public void SendProgress<TResults>(SendOrPostCallback d, int progressPercentage, TResults results)
        {
            SendProgress(d, progressPercentage, results, dummyTaskID);
        }

        /// <summary>
        /// Raises the progress event synchronously on the proper thread.
        /// </summary>
        /// <typeparam name="TResults">The type of the incremental results.</typeparam>
        /// <param name="d">A <see cref="SendOrPostCallback"/> bound to one of the <c>OnProgressChanged</c> methods in this class.</param>
        /// <param name="progressPercentage">An <see cref="Int32"/> between 0 and 100 that indicates the progress of the operation.</param>
        /// <param name="results">The incremental results of the operation, packed into a single <c>struct</c> if there are more than one.</param>
        /// <param name="userState">An object that uniquely identifies a specific invocation of the operation.</param>
        public void SendProgress<TResults>(SendOrPostCallback d, int progressPercentage, TResults results, object userState)
        {
            OperationState opState = GetOperationState(userState);
            opState.Operation.SynchronizationContext.Send(d, new ProgressChangedEventArgs<TResults>(progressPercentage, results, userState));
        }

        /// <summary>
        /// Raises the progress event asynchronously on the proper thread.
        /// </summary>
        /// <param name="d">A <see cref="SendOrPostCallback"/> bound to one of the <c>OnProgressChanged</c> methods in this class.</param>
        /// <param name="progressPercentage">An <see cref="Int32"/> between 0 and 100 that indicates the progress of the operation.</param>
        public void PostProgress(SendOrPostCallback d, int progressPercentage)
        {
            PostProgress(d, progressPercentage, dummyTaskID);
        }

        /// <summary>
        /// Raises the progress event asynchronously on the proper thread.
        /// </summary>
        /// <param name="d">A <see cref="SendOrPostCallback"/> bound to one of the <c>OnProgressChanged</c> methods in this class.</param>
        /// <param name="progressPercentage">An <see cref="Int32"/> between 0 and 100 that indicates the progress of the operation.</param>
        /// <param name="userState">An object that uniquely identifies a specific invocation of the operation.</param>
        public void PostProgress(SendOrPostCallback d, int progressPercentage, object userState)
        {
            OperationState opState = GetOperationState(userState);
            opState.Operation.Post(d, new ProgressChangedEventArgs(progressPercentage, userState));
        }

        /// <summary>
        /// Raises the progress event asynchronously on the proper thread.
        /// </summary>
        /// <typeparam name="TResults">The type of the incremental results.</typeparam>
        /// <param name="d">A <see cref="SendOrPostCallback"/> bound to one of the <c>OnProgressChanged</c> methods in this class.</param>
        /// <param name="progressPercentage">An <see cref="Int32"/> between 0 and 100 that indicates the progress of the operation.</param>
        /// <param name="results">The incremental results of the operation, packed into a single <c>struct</c> if there are more than one.</param>
        public void PostProgress<TResults>(SendOrPostCallback d, int progressPercentage, TResults results)
        {
            PostProgress(d, progressPercentage, results, dummyTaskID);
        }

        /// <summary>
        /// Raises the progress event asynchronously on the proper thread.
        /// </summary>
        /// <typeparam name="TResults">The type of the incremental results.</typeparam>
        /// <param name="d">A <see cref="SendOrPostCallback"/> bound to one of the <c>OnProgressChanged</c> methods in this class.</param>
        /// <param name="progressPercentage">An <see cref="Int32"/> between 0 and 100 that indicates the progress of the operation.</param>
        /// <param name="results">The incremental results of the operation, packed into a single <c>struct</c> if there are more than one.</param>
        /// <param name="userState">An object that uniquely identifies a specific invocation of the operation.</param>
        public void PostProgress<TResults>(SendOrPostCallback d, int progressPercentage, TResults results, object userState)
        {
            OperationState opState = GetOperationState(userState);
            opState.Operation.Post(d, new ProgressChangedEventArgs<TResults>(progressPercentage, results, userState));
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// When overridden in a derived class, calls <c>EndInvoke</c> on the delegate <paramref name="d"/>.
        /// </summary>
        /// <param name="d">The <typeparamref name="TDelegate"/> on which to call <c>EndInvoke</c>.</param>
        /// <param name="iar">The <see cref="IAsyncResult"/> that was returned by <c>BeginInvoke</c>.</param>
        /// <param name="output">The output of the operation (its return value and any <c>out</c> and <c>ref</c> arguments).</param>
        protected abstract void CallEndInvoke(TDelegate d, IAsyncResult iar, out TOutput output);

        /// <summary>
        /// Raises the <see cref="OperationCompleted"/> event.
        /// </summary>
        /// <param name="args">The arguments to the event handler.</param>
        /// <remarks>This method's signature is designed to match the <see cref="SendOrPostCallback"/> type to allow it to be used with <see cref="AsyncOperation.PostOperationCompleted"/>.</remarks>
        protected virtual void OnOperationCompleted(object args)
        {
            if (OperationCompleted != null)
            {
                OperationCompleted(this, (AsyncCompletedEventArgs<TOutput>)args);
            }
        }

        #endregion

        #region Private Methods

        private void Dispose(bool disposing)
        {
            if (this.disposed) return;
            ILease lease = (ILease)GetLifetimeService();
            if (lease != null && lease.CurrentState != LeaseState.Null)
            {
                lease.Unregister((ISponsor)server);
            }
            this.disposed = true;
        }

        private OperationState GetOperationState(object userState)
        {
            lock (opStateForTaskID)
            {
                OperationState value = null;
                if (opStateForTaskID.TryGetValue(userState, out value))
                {
                    return value;
                }
                else
                {
                    throw new ApplicationException(string.Format("No operation found for task ID {0}.", userState));
                }
            }
        }

        #endregion

        #region Protected Fields

        /// <summary>
        /// The server object.
        /// </summary>
        protected readonly TServer server;

        /// <summary>
        /// The client object (derived from <see cref="ClientBase&lt;TServer&gt;"/>) that contains this object.
        /// </summary>
        protected readonly TClient client;

        #endregion

        #region Private Fields

        private bool disposed = false;
        private readonly object dummyTaskID = new object();
        private readonly Dictionary<object, OperationState> opStateForTaskID = new Dictionary<object, OperationState>();

        #endregion

        #region Protected Types

        /// <summary>
        /// Represents the state of a specific instance of the operation.
        /// </summary>
        protected internal class OperationState
        {
            /// <summary>
            /// Constructs a new instance of the <see cref="OperationState"/> class.
            /// </summary>
            /// <param name="userState">An object that uniquely identifies a specific invocation of the operation.</param>
            public OperationState(object userState)
            {
                this.Operation = AsyncOperationManager.CreateOperation(userState);
                this.Cancelled = false;
            }

            #region Public Properties

            /// <summary>
            /// Gets an object that allows callbacks to be marshalled to the proper thread.
            /// </summary>
            public AsyncOperation Operation;

            /// <summary>
            /// Gets or sets a value indicating that the client has requested the server to abort the operation.
            /// </summary>
            public bool Cancelled;

            #endregion
        }

        #endregion
    }

    /// <summary>
    /// A specialization of <see cref="OperationTracker&lt;TClient, TDelegate, TOutput&gt;"/> for operations that don't produce any output.
    /// </summary>
    /// <typeparam name="TServer">The server class or interface.</typeparam>
    /// <typeparam name="TClient">The client class that contains this tracker.</typeparam>
    /// <typeparam name="TDelegate">A delegate type that represents the signature of the basic (synchronous) method.</typeparam>
    public abstract class OperationTracker<TServer, TClient, TDelegate> : OperationTracker<TServer, TClient, TDelegate, object> where TClient : ClientBase<TServer>
    {
        #region Construction

        /// <summary>
        /// Constructs a new instance of the <see cref="OperationTracker&lt;TServer, TClient, TDelegate&gt;"/> class.
        /// </summary>
        /// <param name="server">The server object associated with the client object.</param>
        /// <param name="client">The client object that contains this object.</param>
        public OperationTracker(TServer server, TClient client)
            : base(server, client)
        {
        }

        #endregion

        #region Public Events

        /// <summary>
        /// Indicates to the client that an operation has completed.
        /// </summary>
        public new event AsyncCompletedEventHandler OperationCompleted;

        #endregion

        #region Protected Methods

        /// <summary>
        /// Raises the <see cref="OperationCompleted"/> event.
        /// </summary>
        /// <param name="args">The arguments to the event handler.</param>
        /// <remarks>This method's signature is designed to match the <see cref="SendOrPostCallback"/> type to allow it to be used with <see cref="AsyncOperation.PostOperationCompleted"/>.</remarks>
        protected override void OnOperationCompleted(object args)
        {
            if (OperationCompleted != null)
            {
                OperationCompleted(this, (AsyncCompletedEventArgs)args);
            }
        }

        /// <summary>
        /// Calls the other overload without the output argument.
        /// </summary>
        /// <param name="d">The <typeparamref name="TDelegate"/> on which to call <c>EndInvoke</c>.</param>
        /// <param name="iar">The <see cref="IAsyncResult"/> that was returned by <c>BeginInvoke</c>.</param>
        /// <param name="output">Returns <c>null</c>.</param>
        protected sealed override void CallEndInvoke(TDelegate d, IAsyncResult iar, out object output)
        {
            CallEndInvoke(d, iar);
            output = null;
        }

        /// <summary>
        /// When overridden in a derived class, calls <c>EndInvoke</c> on the delegate <paramref name="d"/>.
        /// </summary>
        /// <param name="d">The <typeparamref name="TDelegate"/> on which to call <c>EndInvoke</c>.</param>
        /// <param name="iar">The <see cref="IAsyncResult"/> that was returned by <c>BeginInvoke</c>.</param>
        protected abstract void CallEndInvoke(TDelegate d, IAsyncResult iar);

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Philips Healthcare
Israel Israel
I got my B.Sc. in Mathematics and Computer Science from Tel Aviv University in 1997. Since then I have developed software in UNIX, Win32 and .NET. I currently live in Haifa.

Comments and Discussions