123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601 |
- #region --------------- Copyright Dresser Wayne Pignone -------------
- /*
- * $Log: /Wrk/WayneLibraries/Wrk/AsyncManager/AsyncManager.cs $
- *
- * 12 08-03-19 12:09 Mattias.larsson
- * Added GetOperationsByData() and Clear().
- *
- * 11 08-01-31 12:12 Mattias.larsson
- *
- * 10 07-05-28 9:40 roger.månsson
- * Be more tolerant to null as user token.
- *
- * 9 07-04-16 12:37 roger.månsson
- * Added Data, as an application-defined extra value that can be stored
- * with the async operation.
- *
- * 8 07-03-22 9:53 Mattias.larsson
- *
- * 7 07-03-13 12:53 roger.månsson
- * Fixed the TryGetOperation return value when getting an operation but
- * with wrong type.
- *
- * 6 07-02-15 17:48 roger.månsson
- * FxCop updates
- *
- * 5 07-01-12 16:36 roger.månsson
- * Only log when actually cleaning operations from the internal list
- */
- #endregion
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using Wayne.Lib.Log;
- namespace Wayne.Lib.AsyncManager
- {
- /// <summary>
- /// Async manager is used to handle asynchronous call tracking. It saves data about the
- /// asynchronous call so it can be signaled later on. This implementation is based on a operation id type that
- /// is defined by the type parameter TOperationId. For example if we are communicating with some equipment and
- /// there is a sequence number in the communication, this can be the operation id in the async manager. On the
- /// </summary>
- public class AsyncManager<TOperationId> : IIdentifiableEntity, IDisposable
- {
- #region Fields
-
- private int id;
- private IIdentifiableEntity parentEntity;
- private readonly IServiceLocator serviceLocator;
- private List<AsyncOperation<TOperationId>> outstandingOperationList = new List<AsyncOperation<TOperationId>>();
- private object outstandingOperationListLock = new object();
- private ITimer cleaningTimer;
- private TimeSpan cleanOutstandingOperationsOlderThan;
- private readonly Func<TOperationId> nextOperationId = null;
- #endregion
- #region Construction
- /// <summary>
- /// Initializes a new instance of the AsyncManager.
- /// Uses a default timeout of 1 hour.
- /// </summary>
- /// <param name="id">The Id.</param>
- /// <param name="parentEntity">The parent entity.</param>
- protected AsyncManager(int id, IIdentifiableEntity parentEntity)
- : this(id, parentEntity, ServiceContainerFactory.Create())
- {
- }
- /// <summary>
- /// Initializes a new instance of the AsyncManager.
- /// Uses a default timeout of 1 hour.
- /// </summary>
- /// <param name="id">The Id.</param>
- /// <param name="parentEntity">The parent entity.</param>
- /// <param name="serviceLocator"></param>
- protected AsyncManager(int id, IIdentifiableEntity parentEntity, IServiceLocator serviceLocator)
- : this(id, parentEntity, serviceLocator, TimeSpan.FromHours(1))
- {
- }
- /// <summary>
- /// Initializes a new instance of the AsyncManager with a specific cleanOutstandingOperations max age.
- /// The timer is triggered after a calculated period between 30 seconds and 10 minutes,
- /// if the TimeSpan is not null.
- /// </summary>
- /// <param name="id">The Id.</param>
- /// <param name="parentEntity">The parent entity.</param>
- /// <param name="serviceLocator"></param>
- /// <param name="cleanOutstandingOperationsOlderThan">Sets the maximum age an operation can achieve. Minimum age is 1 minute.
- /// If null no timer is created.</param>
- protected AsyncManager(int id, IIdentifiableEntity parentEntity, IServiceLocator serviceLocator, TimeSpan? cleanOutstandingOperationsOlderThan)
- {
- this.id = id;
- this.parentEntity = parentEntity;
- this.serviceLocator = serviceLocator;
- ITimerFactory timerFactory = serviceLocator.GetServiceOrDefault<ITimerFactory>(() => new WayneTimerFactory());
- if (cleanOutstandingOperationsOlderThan.HasValue)
- {
- this.cleanOutstandingOperationsOlderThan = cleanOutstandingOperationsOlderThan.Value;
- TimeSpan timerDurationAndPeriod =
- CalculatecleaningTimerTriggerLengthFromcleanOutstandingOperationsOlderThan();
- cleaningTimer = timerFactory.Create(IdentifiableEntity.NoId, this, "CleaningTimer");
- cleaningTimer.OnTimeout += CleanOutstandingOperations;
- cleaningTimer.Change(timerDurationAndPeriod, timerDurationAndPeriod);
- }
- }
- /// <summary>
- /// Initializes a new instance of the AsyncManager with a specific cleanOutstandingOperations max age.
- /// The timer is triggered after a calculated period between 30 seconds and 10 minutes,
- /// if the TimeSpan is not null.
- /// </summary>
- /// <param name="id">The Id.</param>
- /// <param name="parentEntity">The parent entity.</param>
- /// <param name="serviceLocator"></param>
- /// <param name="cleanOutstandingOperationsOlderThan">Sets the maximum age an operation can achieve. Minimum age is 1 minute.
- /// If null no timer is created.</param>
- /// <param name="nextOperationId"></param>
- public AsyncManager(int id, IIdentifiableEntity parentEntity, IServiceLocator serviceLocator, TimeSpan? cleanOutstandingOperationsOlderThan, Func<TOperationId> nextOperationId)
- {
- this.id = id;
- this.parentEntity = parentEntity;
- this.serviceLocator = serviceLocator;
- this.nextOperationId = nextOperationId;
- ITimerFactory timerFactory = serviceLocator.GetServiceOrDefault<ITimerFactory>(() => new WayneTimerFactory());
- if (cleanOutstandingOperationsOlderThan.HasValue)
- {
- this.cleanOutstandingOperationsOlderThan = cleanOutstandingOperationsOlderThan.Value;
- TimeSpan timerDurationAndPeriod =
- CalculatecleaningTimerTriggerLengthFromcleanOutstandingOperationsOlderThan();
- cleaningTimer = timerFactory.Create(IdentifiableEntity.NoId, this, "CleaningTimer");
- cleaningTimer.OnTimeout += CleanOutstandingOperations;
- cleaningTimer.Change(timerDurationAndPeriod, timerDurationAndPeriod);
- }
- }
- /// <summary>
- /// Finalizer
- /// </summary>
- ~AsyncManager()
- {
- Dispose(false);
- }
- #endregion
- #region Protected virtual methods
- /// <summary>
- /// Method that should be overridden by descendant classes in order to create
- /// a new operation id to be used by the next operation.
- /// </summary>
- /// <returns></returns>
- protected virtual TOperationId CreateNextOperationId()
- {
- return nextOperationId();
- }
- #endregion
- #region Public Methods
- /// <summary>
- /// Registers an operation with the owner, result delegate and a user token, so it can be found later on. It creates an operation id that can be read from the AsyncOperation that
- /// is returned from the function.
- /// </summary>
- /// <typeparam name="TResultEventArgs">Type of the EventArgs to the result delegate.</typeparam>
- /// <param name="owner">The object that is going to be set as sender in the result delegate invocation.</param>
- /// <param name="resultDelegate">Delegate to be</param>
- /// <param name="userToken">User token that is returned in the calback invokation.</param>
- public AsyncOperation<TOperationId, TResultEventArgs> RegisterOperation<TResultEventArgs>(object owner, EventHandler<TResultEventArgs> resultDelegate, object userToken) where TResultEventArgs : EventArgs
- {
- return RegisterOperation(owner, resultDelegate, userToken, null);
- }
- /// <summary>
- /// Registers an operation with the owner, result delegate and a user token, so it can be found later on. It creates an operation id that can be read from the AsyncOperation that
- /// is returned from the function. The application can an application-defined object that is to be stored with the operation.
- /// </summary>
- /// <typeparam name="TResultEventArgs">Type of the EventArgs to the result delegate.</typeparam>
- /// <param name="owner">The object that is going to be set as sender in the result delegate invocation.</param>
- /// <param name="resultDelegate">Delegate to be</param>
- /// <param name="userToken">User token that is returned in the calback invokation.</param>
- /// <param name="data">Application defined data.</param>
- public AsyncOperation<TOperationId, TResultEventArgs> RegisterOperation<TResultEventArgs>(object owner, EventHandler<TResultEventArgs> resultDelegate, object userToken, object data) where TResultEventArgs : EventArgs
- {
- lock (outstandingOperationListLock)
- {
- TOperationId newOperationId = CreateNextOperationId();
- AsyncOperation<TOperationId, TResultEventArgs> asyncOperation = new AsyncOperation<TOperationId, TResultEventArgs>(owner, newOperationId, userToken, data, resultDelegate, cleanOutstandingOperationsOlderThan);
- outstandingOperationList.Add(asyncOperation);
- asyncOperation.OnOperationCompleted += asyncOperation_OnOperationCompleted;
- return asyncOperation;
- }
- }
- /// <summary>
- /// Retrieves an outstanding operation by the operation id
- /// </summary>
- /// <typeparam name="TResultEventArgs">The type that the operation is expected to be containing, so we know what we expect to get as a result.</typeparam>
- /// <param name="operationId">The operation Id to search for.</param>
- /// <param name="asyncOperation">Out parameter that returns the matching operation. Null if it is not found.</param>
- /// <returns>True if a matching operation is found, otherwise false.</returns>
- public bool TryGetOperation<TResultEventArgs>(TOperationId operationId, out AsyncOperation<TOperationId, TResultEventArgs> asyncOperation) where TResultEventArgs : EventArgs
- {
- asyncOperation = null;
- lock (outstandingOperationListLock)
- {
- foreach (AsyncOperation<TOperationId> op in outstandingOperationList)
- {
- if ((op.Id.Equals(operationId)))
- {
- asyncOperation = op as AsyncOperation<TOperationId, TResultEventArgs>;
- if (asyncOperation != null)
- return true;
- else
- return false;
- }
- }
- }
- return false;
- }
- /// <summary>
- /// Checks whether the requested Operation exists in the list.
- /// </summary>
- /// <param name="operationId">The operation Id to search for.</param>
- /// <returns>True if a matching operation is found, otherwise false.</returns>
- public bool OperationExists(TOperationId operationId)
- {
- lock (outstandingOperationListLock)
- {
- foreach (AsyncOperation<TOperationId> op in outstandingOperationList)
- if ((op.Id.Equals(operationId)))
- return true;
- }
- return false;
- }
- /// <summary>
- /// Returns the EventArgs-type if a matching operation is found, otherwise null.
- /// </summary>
- /// <param name="operationId">The operation Id to search for.</param>
- /// <returns>The EventArgs-type if a matching operation is found, otherwise null.</returns>
- public Type GetOperationType(TOperationId operationId)
- {
- lock (outstandingOperationListLock)
- {
- foreach (AsyncOperation<TOperationId> op in outstandingOperationList)
- if ((op.Id.Equals(operationId)))
- return op.ResultEventArgsType;
- }
- return null;
- }
- /// <summary>
- /// Gets a list of outstanding operations that matches the the specified userToken. Checks and converts the operation to the specified type.
- /// </summary>
- /// <typeparam name="TResultEventArgs">Type of the result event args.</typeparam>
- /// <param name="userToken">User token to match the operation against.</param>
- /// <param name="asyncOperationList">List of matching operations.</param>
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")]
- public void GetOperationsByUserToken<TResultEventArgs>(object userToken, out AsyncOperation<TOperationId, TResultEventArgs>[] asyncOperationList) where TResultEventArgs : EventArgs
- {
- List<AsyncOperation<TOperationId, TResultEventArgs>> list = new List<AsyncOperation<TOperationId, TResultEventArgs>>();
- lock (outstandingOperationListLock)
- {
- foreach (AsyncOperation<TOperationId> op in outstandingOperationList)
- {
- if (op.UserToken != null)
- {
- if ((op.UserToken.Equals(userToken)))
- {
- AsyncOperation<TOperationId, TResultEventArgs> asyncOperation = op as AsyncOperation<TOperationId, TResultEventArgs>;
- if (asyncOperation != null)
- list.Add(asyncOperation);
- }
- }
- else
- {
- if (userToken == null)
- {
- AsyncOperation<TOperationId, TResultEventArgs> asyncOperation = op as AsyncOperation<TOperationId, TResultEventArgs>;
- if (asyncOperation != null)
- list.Add(asyncOperation);
- }
- }
- }
- }
- asyncOperationList = list.ToArray();
- }
- /// <summary>
- /// Gets an outstanding operation list using the userToken as argument. Does not check the type of the operation.
- /// </summary>
- /// <param name="userToken">User token that is specified for the outstanding operation.</param>
- /// <returns>A list of Async operations.</returns>
- public AsyncOperation<TOperationId>[] GetOperationsByUserToken(object userToken)
- {
- List<AsyncOperation<TOperationId>> list = new List<AsyncOperation<TOperationId>>();
- lock (outstandingOperationListLock)
- {
- foreach (AsyncOperation<TOperationId> op in outstandingOperationList)
- {
- if (op.UserToken != null)
- {
- if ((op.UserToken.Equals(userToken)))
- {
- list.Add(op);
- }
- }
- else
- {
- if (userToken == null) //If the requesting user token also is null, it is a valid match!
- list.Add(op);
- }
- }
- }
- return list.ToArray();
- }
- /// <summary>
- /// Gets a list of outstanding operations that matches the the specified data object. Checks and converts the operation to the specified type.
- /// </summary>
- /// <typeparam name="TResultEventArgs">Type of the result event args.</typeparam>
- /// <param name="data">Data object to match the operation against.</param>
- /// <param name="asyncOperationList">List of matching operations.</param>
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")]
- public void GetOperationsByData<TResultEventArgs>(object data, out AsyncOperation<TOperationId, TResultEventArgs>[] asyncOperationList) where TResultEventArgs : EventArgs
- {
- List<AsyncOperation<TOperationId, TResultEventArgs>> list = new List<AsyncOperation<TOperationId, TResultEventArgs>>();
- lock (outstandingOperationListLock)
- {
- foreach (AsyncOperation<TOperationId> op in outstandingOperationList)
- {
- if (op.Data != null)
- {
- if ((op.Data.Equals(data)))
- {
- AsyncOperation<TOperationId, TResultEventArgs> asyncOperation = op as AsyncOperation<TOperationId, TResultEventArgs>;
- if (asyncOperation != null)
- list.Add(asyncOperation);
- }
- }
- else
- {
- if (data == null)
- {
- AsyncOperation<TOperationId, TResultEventArgs> asyncOperation = op as AsyncOperation<TOperationId, TResultEventArgs>;
- if (asyncOperation != null)
- list.Add(asyncOperation);
- }
- }
- }
- }
- asyncOperationList = list.ToArray();
- }
- /// <summary>
- /// Gets an outstanding operation list using the data object as argument. Does not check the type of the operation.
- /// </summary>
- /// <param name="data">User token that is specified for the outstanding operation.</param>
- /// <returns>A list of Async operations.</returns>
- public AsyncOperation<TOperationId>[] GetOperationsByData(object data)
- {
- List<AsyncOperation<TOperationId>> list = new List<AsyncOperation<TOperationId>>();
- lock (outstandingOperationListLock)
- {
- foreach (AsyncOperation<TOperationId> op in outstandingOperationList)
- {
- if (op.Data != null)
- {
- if ((op.Data.Equals(data)))
- {
- list.Add(op);
- }
- }
- else
- {
- if (data == null) //If the requesting user token also is null, it is a valid match!
- list.Add(op);
- }
- }
- }
- return list.ToArray();
- }
- /// <summary>
- /// Clear all outstanding operations.
- /// </summary>
- public void Clear()
- {
- lock (outstandingOperationListLock)
- {
- outstandingOperationList.Clear();
- }
- }
- #endregion
- #region Private Methods
- /// <summary>
- /// Calculates the timespan based on simple rules.
- /// </summary>
- /// <returns>The timespan object to be used for the cleaningTimers period and duration</returns>
- private TimeSpan CalculatecleaningTimerTriggerLengthFromcleanOutstandingOperationsOlderThan()
- {
- TimeSpan result;
- if (cleanOutstandingOperationsOlderThan >= TimeSpan.FromHours(1))
- {
- result = TimeSpan.FromMinutes(10);
- }
- else if (cleanOutstandingOperationsOlderThan <= TimeSpan.FromMinutes(1))
- {
- result = TimeSpan.FromSeconds(30);
- //Will never accept less values than 1 minute as oldest time an operation might be.
- cleanOutstandingOperationsOlderThan = TimeSpan.FromMinutes(1);
- }
- else
- {
- //The formula is:
- // x = y - b / m
- // m = (y2 - y1) / (x2 - x1) => (3600 * 10^3 - 60 * 10^3) / (600 * 10 ^3 - 30 * 10^3) => (354/57) ~= 6,2105...
- // b = y1 - m * x1 => 60 * 10^3 - (354/57) * 30 * 10^3 ~= -126315,789476...
- result = TimeSpan.FromMilliseconds((cleanOutstandingOperationsOlderThan.TotalMilliseconds + 126315.78947368421052631578947368) / (354.0 / 57.0));
- }
- return result;
- }
- /// <summary>
- /// Event handler that is called when an asynchronous operation has completed. It will remove the
- /// operation from the outstanding operations list.
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- void asyncOperation_OnOperationCompleted(object sender, EventArgs e)
- {
- AsyncOperation<TOperationId> asyncOperation = sender as AsyncOperation<TOperationId>;
- if (asyncOperation != null)
- {
- lock (outstandingOperationListLock)
- {
- if (outstandingOperationList.Contains(asyncOperation))
- {
- outstandingOperationList.Remove(asyncOperation);
- }
- }
- }
- }
- /// <summary>
- /// Timer method that is called to clean the outstanding queue. For now, the requests must not stay
- /// more than 1h in the outstanding list.
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- void CleanOutstandingOperations(object sender, EventArgs e)
- {
- AsyncOperation<TOperationId>[] abandonedOperations;
- lock (outstandingOperationListLock)
- {
- abandonedOperations = outstandingOperationList.Where(x => x.Timeout.IsTimedOut).ToArray();
- }
- using (DebugLogger debugLogger = new DebugLogger(this))
- {
- if (abandonedOperations.Length > 0)
- {
- //Remove the items that should be removed.
- if (debugLogger.IsActive())
- {
- debugLogger.Add("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO");
- debugLogger.Add("Cleaning the outstanding operations.");
- debugLogger.Add("---------------------------------------------------");
- }
- foreach (AsyncOperation<TOperationId> abandonedOperation in abandonedOperations)
- {
- if (debugLogger.IsActive())
- {
- debugLogger.Add("Removing operation: ");
- debugLogger.Add(abandonedOperation.ToString());
- }
- try
- {
- abandonedOperation.Abandoned();
- }
- catch (Exception exception)
- {
- //Logger.AddExceptionLogEntry(new ExceptionLogEntry(this, ErrorLogSeverity.Recoverable, "Exception when handling abandoned async operation " + abandonedOperation, exception));
- }
- }
- if (debugLogger.IsActive())
- debugLogger.Add("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO");
- }
- }
- //Remove the found operations, if not removed by abandoned- logic.
- lock (outstandingOperationListLock)
- {
- abandonedOperations.ForEach(x => outstandingOperationList.Remove(x));
- }
- }
- #endregion
- #region IIdentifiableEntity Members
- /// <summary>
- /// Async manager Id (for logging)
- /// </summary>
- public int Id
- {
- get { return id; }
- }
- /// <summary>
- /// Entity type (for logging)
- /// </summary>
- public string EntityType
- {
- get { return "AsyncManager"; }
- }
- /// <summary>
- /// This is used by the logger and should never be set by inheriting classes
- /// </summary>
- public string FullEntityName { get; set; }
- /// <summary>
- /// Entity sub type (for logging)
- /// </summary>
- public virtual string EntitySubType
- {
- get { return ""; }
- }
- /// <summary>
- /// Parent entity (for logging)
- /// </summary>
- public IIdentifiableEntity ParentEntity
- {
- get { return parentEntity; }
- }
- #endregion
- #region IDisposable Members
- /// <summary>
- /// Dispsose.
- /// </summary>
- /// <param name="disposing"></param>
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- if (cleaningTimer != null)
- cleaningTimer.Dispose();
- }
- }
- /// <summary>
- /// Dispsose.
- /// </summary>
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
- #endregion
- }
- }
|