///
///
///
public enum ThreadCallback
{
///
/// This is the thread that InvokeAsync() is called on
///
AsyncCallerThread,
///
/// This is the thread the InvokeAsync() spawned
///
WorkerThread
}
///
/// This class is responsible for tracking the IAsyncResult from a BeginInvoke(),
/// Catching exceptions from EndInvoke(), and Marshalling the data or exception to the
/// correct thread
///
/// result of function call
public abstract class ApmHelperBase : IDisposable
{
///
/// lock to preserve thread safety on getting/setting IAsyncResult
///
private readonly object _iAsynclock = new object();
///
/// Pointer to method to execute when EndInvoke() throws exception
///
private readonly Action _defaultExceptionHandler;
///
/// Method to get begin, end invoke functions from
///
private readonly Delegate _function;
///
/// cache of BeginInvoke method
///
private readonly MethodInfo _beginInvokeMethod;
///
/// cache of EndInvoke method
///
private readonly MethodInfo _endInvokeMethod;
///
/// cache of callback that all BeginInvoke()s wire to
/// that will issue callback to correct thread
///
private readonly AsyncCallback _postCallback;
///
/// Cache of the current call from BeginInvoke()
///
private IAsyncResult _current;
///
/// cache the current synchronization context
///
private readonly SynchronizationContext _context;
///
/// Offer ability to timeout on async calls
///
private readonly Timer _timer;
///
/// lock for timer (timer callback on bg thread)
///
private readonly object _timerLock = new object();
///
/// optional timeout to set for calls
///
private int _timeout;
///
/// ctor
///
protected ApmHelperBase(Delegate function, Action exceptionHandler)
: this(function, exceptionHandler, ThreadCallback.AsyncCallerThread) { }
///
/// ctor
///
/// function user is going to bind to..This will actually be some func but we don't care here. Ultimately
/// this function will be called asynchronously on the bg thread
/// optional method user wants to be notified if async call throws exceptino
/// Which thread should callbacks occur on
protected ApmHelperBase(Delegate function, Action exceptionHandler, ThreadCallback threadCallback)
{
if (null == function) throw new ArgumentNullException("function");
this._function = function;
// cache the methods
Type type = function.GetType();
this._beginInvokeMethod = type.GetMethod("BeginInvoke");
this._endInvokeMethod = type.GetMethod("EndInvoke");
// if no ex handler, we use our own
this._defaultExceptionHandler = exceptionHandler ?? DefaultExceptionHandler;
// all async calls will get pointed to this callback
this._postCallback = this.PostCallbackToCorrectThread;
// cache which thread user wants callbacks on..he can change later if he wants
this.TheThreadCallback = threadCallback;
// cache the sync context
this._context = SynchronizationContext.Current;
// get our timer ready to support timeouts
this._timer = new Timer(this.TimerCallback);
// start with no timeout set. User must specify
this.TurnTimerOff();
}
///
/// Setting this to any postive value will turn on the timer for each call.
/// Less than or equal to 0 will turn it off.
///
/// can't be used when IssueCallbacksOnInvokesAsync == true
public int MillisecondTimeout
{
get { return this._timeout; }
set
{
this._timeout = value;
if (value > 0 && this.IssueCallbacksOnInvokesAsync)
throw new InvalidOperationException("Can't use a timer if you want all callbacks. If you want to use the timer, set IssueCallbacksOnInvokesAsyn = false;");
}
}
///
/// User can set if they want callbacks to occur on the
/// thread this object is called InvokeAsycn(), or on the thread
/// spawned from the BeingInvoke() method.
///
/// This applies to the exception handler as well
public ThreadCallback TheThreadCallback { get; set; }
///
/// Provides the asyncresult in a threadsafe manor
///
public IAsyncResult CurrentIAsyncResult
{
get { lock (this._iAsynclock) return this._current; }
private set { lock (this._iAsynclock) this._current = value; }
}
///
/// Sets IssueCallbacksOnInvokesAsync to false and
/// wipes out the CurrentIAsyncResult so no callback fires
///
/// Subsequent calls to InvokeAsync() will
/// set the CurrentIAsyncResult so the last InvokeAsync() will get the callback
public void Cancel()
{
this.IssueCallbacksOnInvokesAsync = false;
this.CurrentIAsyncResult = null;
}
///
/// If true, APMHelper will issue callbacks on ALL BeginInvokes(), otherwise
/// only the last InvokeAsync() gets the callback
///
public bool IssueCallbacksOnInvokesAsync { get; set; }
///
/// Ignores all outstanding calls.
///
/// You could actually start using it again
public void Dispose()
{
this.Cancel();
}
///
/// User should convert his arguments in order into args parm
///
/// T1, T2..etc
/// method user wants results pumped to
/// method user wants exceptions pumped into
protected void InvokeAsync(List