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<TServer>"/>) 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<TClient, TDelegate, TOutput>"/> 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<TServer, TClient, TDelegate>"/> 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
}
}