//----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- namespace System.Activities { using System; using System.Activities.Hosting; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Runtime; using System.Threading; [Fx.Tag.XamlVisible(false)] public sealed class WorkflowInvoker { static AsyncCallback cancelCallback; static AsyncCallback invokeCallback; WorkflowInstanceExtensionManager extensions; Dictionary pendingInvokes; SendOrPostCallback raiseInvokeCompletedCallback; object thisLock; Activity workflow; public WorkflowInvoker(Activity workflow) { if (workflow == null) { throw FxTrace.Exception.ArgumentNull("workflow"); } this.workflow = workflow; this.thisLock = new object(); } public event EventHandler InvokeCompleted; public WorkflowInstanceExtensionManager Extensions { get { if (this.extensions == null) { this.extensions = new WorkflowInstanceExtensionManager(); } return this.extensions; } } Dictionary PendingInvokes { get { if (this.pendingInvokes == null) { this.pendingInvokes = new Dictionary(); } return this.pendingInvokes; } } SendOrPostCallback RaiseInvokeCompletedCallback { get { if (this.raiseInvokeCompletedCallback == null) { this.raiseInvokeCompletedCallback = Fx.ThunkCallback(new SendOrPostCallback(this.RaiseInvokeCompleted)); } return this.raiseInvokeCompletedCallback; } } object ThisLock { get { return this.thisLock; } } [Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")] public static IDictionary Invoke(Activity workflow) { return Invoke(workflow, ActivityDefaults.InvokeTimeout); } [Fx.Tag.InheritThrows(From = "Invoke")] public static IDictionary Invoke(Activity workflow, TimeSpan timeout) { return Invoke(workflow, timeout, null); } [Fx.Tag.InheritThrows(From = "Invoke")] public static IDictionary Invoke(Activity workflow, IDictionary inputs) { return Invoke(workflow, inputs, ActivityDefaults.InvokeTimeout, null); } [Fx.Tag.InheritThrows(From = "Invoke")] public static IDictionary Invoke(Activity workflow, IDictionary inputs, TimeSpan timeout) { return Invoke(workflow, inputs, timeout, null); } [Fx.Tag.InheritThrows(From = "Invoke")] [SuppressMessage(FxCop.Category.Design, FxCop.Rule.ConsiderPassingBaseTypesAsParameters, Justification = "Generic needed for type inference")] public static TResult Invoke(Activity workflow) { return Invoke(workflow, null); } [Fx.Tag.InheritThrows(From = "Invoke")] [SuppressMessage(FxCop.Category.Design, FxCop.Rule.ConsiderPassingBaseTypesAsParameters, Justification = "Generic needed for type inference")] public static TResult Invoke(Activity workflow, IDictionary inputs) { return Invoke(workflow, inputs, ActivityDefaults.InvokeTimeout); } [Fx.Tag.InheritThrows(From = "Invoke")] [SuppressMessage(FxCop.Category.Design, FxCop.Rule.ConsiderPassingBaseTypesAsParameters, Justification = "Generic needed for type inference")] public static TResult Invoke(Activity workflow, IDictionary inputs, TimeSpan timeout) { IDictionary dummyOutputs; return Invoke(workflow, inputs, out dummyOutputs, timeout); } [Fx.Tag.InheritThrows(From = "Invoke")] [SuppressMessage(FxCop.Category.Design, FxCop.Rule.ConsiderPassingBaseTypesAsParameters, Justification = "Generic needed for type inference")] [SuppressMessage(FxCop.Category.Design, FxCop.Rule.AvoidOutParameters, Justification = "Arch approved design. Requires the out argument for extra information provided")] public static TResult Invoke(Activity workflow, IDictionary inputs, out IDictionary additionalOutputs, TimeSpan timeout) { TimeoutHelper.ThrowIfNegativeArgument(timeout); if (inputs != null) { additionalOutputs = Invoke(workflow, inputs, timeout, null); } else { additionalOutputs = Invoke(workflow, timeout, null); } object untypedResult; if (additionalOutputs.TryGetValue("Result", out untypedResult)) { additionalOutputs.Remove("Result"); return (TResult)untypedResult; } else { throw Fx.AssertAndThrow("Activity should always have a output named \"Result\""); } } [Fx.Tag.InheritThrows(From = "Invoke")] public IAsyncResult BeginInvoke(AsyncCallback callback, object state) { return BeginInvoke(this.workflow, ActivityDefaults.InvokeTimeout, this.extensions, callback, state); } [Fx.Tag.InheritThrows(From = "Invoke")] public IAsyncResult BeginInvoke(TimeSpan timeout, AsyncCallback callback, object state) { TimeoutHelper.ThrowIfNegativeArgument(timeout); return BeginInvoke(this.workflow, timeout, this.extensions, callback, state); } [Fx.Tag.InheritThrows(From = "Invoke")] public IAsyncResult BeginInvoke(IDictionary inputs, AsyncCallback callback, object state) { return BeginInvoke(this.workflow, inputs, ActivityDefaults.InvokeTimeout, this.extensions, callback, state); } [Fx.Tag.InheritThrows(From = "Invoke")] public IAsyncResult BeginInvoke(IDictionary inputs, TimeSpan timeout, AsyncCallback callback, object state) { TimeoutHelper.ThrowIfNegativeArgument(timeout); return BeginInvoke(this.workflow, inputs, timeout, this.extensions, callback, state); } public void CancelAsync(object userState) { if (userState == null) { throw FxTrace.Exception.ArgumentNull("userState"); } AsyncInvokeContext context = this.RemoveFromPendingInvokes(userState); if (context != null) { // cancel does not need a timeout since it's bounded by the invoke timeout if (cancelCallback == null) { cancelCallback = Fx.ThunkCallback(new AsyncCallback(CancelCallback)); } // cancel only throws TimeoutException and shouldnt throw at all if timeout is infinite // cancel does not need to raise InvokeCompleted since the InvokeAsync invocation would raise it IAsyncResult result = context.WorkflowApplication.BeginCancel(TimeSpan.MaxValue, cancelCallback, context); if (result.CompletedSynchronously) { context.WorkflowApplication.EndCancel(result); } } } [Fx.Tag.InheritThrows(From = "Invoke")] public IDictionary EndInvoke(IAsyncResult result) { return WorkflowApplication.EndInvoke(result); } [Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")] public IDictionary Invoke() { return WorkflowInvoker.Invoke(this.workflow, ActivityDefaults.InvokeTimeout, this.extensions); } [Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")] public IDictionary Invoke(TimeSpan timeout) { TimeoutHelper.ThrowIfNegativeArgument(timeout); return WorkflowInvoker.Invoke(this.workflow, timeout, this.extensions); } [Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")] public IDictionary Invoke(IDictionary inputs) { return WorkflowInvoker.Invoke(this.workflow, inputs, ActivityDefaults.InvokeTimeout, this.extensions); } [Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")] public IDictionary Invoke(IDictionary inputs, TimeSpan timeout) { TimeoutHelper.ThrowIfNegativeArgument(timeout); return WorkflowInvoker.Invoke(this.workflow, inputs, timeout, this.extensions); } public void InvokeAsync() { InvokeAsync(ActivityDefaults.InvokeTimeout, null); } public void InvokeAsync(TimeSpan timeout) { InvokeAsync(timeout, null); } public void InvokeAsync(object userState) { InvokeAsync(ActivityDefaults.InvokeTimeout, userState); } public void InvokeAsync(TimeSpan timeout, object userState) { TimeoutHelper.ThrowIfNegativeArgument(timeout); InternalInvokeAsync(null, timeout, userState); } public void InvokeAsync(IDictionary inputs) { InvokeAsync(inputs, null); } public void InvokeAsync(IDictionary inputs, TimeSpan timeout) { InvokeAsync(inputs, timeout, null); } public void InvokeAsync(IDictionary inputs, object userState) { InvokeAsync(inputs, ActivityDefaults.InvokeTimeout, userState); } public void InvokeAsync(IDictionary inputs, TimeSpan timeout, object userState) { if (inputs == null) { throw FxTrace.Exception.ArgumentNull("inputs"); } TimeoutHelper.ThrowIfNegativeArgument(timeout); InternalInvokeAsync(inputs, timeout, userState); } [Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")] static IDictionary Invoke(Activity workflow, TimeSpan timeout, WorkflowInstanceExtensionManager extensions) { if (workflow == null) { throw FxTrace.Exception.ArgumentNull("workflow"); } TimeoutHelper.ThrowIfNegativeArgument(timeout); IDictionary outputs = WorkflowApplication.Invoke(workflow, null, extensions, timeout); if (outputs == null) { return ActivityUtilities.EmptyParameters; } else { return outputs; } } [Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")] static IDictionary Invoke(Activity workflow, IDictionary inputs, TimeSpan timeout, WorkflowInstanceExtensionManager extensions) { if (workflow == null) { throw FxTrace.Exception.ArgumentNull("workflow"); } if (inputs == null) { throw FxTrace.Exception.ArgumentNull("inputs"); } TimeoutHelper.ThrowIfNegativeArgument(timeout); IDictionary outputs = WorkflowApplication.Invoke(workflow, inputs, extensions, timeout); if (outputs == null) { return ActivityUtilities.EmptyParameters; } else { return outputs; } } void AddToPendingInvokes(AsyncInvokeContext context) { lock (ThisLock) { if (this.PendingInvokes.ContainsKey(context.UserState)) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.SameUserStateUsedForMultipleInvokes)); } this.PendingInvokes.Add(context.UserState, context); } } [Fx.Tag.InheritThrows(From = "Invoke")] IAsyncResult BeginInvoke(Activity workflow, IDictionary inputs, TimeSpan timeout, WorkflowInstanceExtensionManager extensions, AsyncCallback callback, object state) { if (inputs == null) { throw FxTrace.Exception.ArgumentNull("inputs"); } TimeoutHelper.ThrowIfNegativeArgument(timeout); return WorkflowApplication.BeginInvoke(workflow, inputs, extensions, timeout, null, null, callback, state); } [Fx.Tag.InheritThrows(From = "Invoke")] IAsyncResult BeginInvoke(Activity workflow, TimeSpan timeout, WorkflowInstanceExtensionManager extensions, AsyncCallback callback, object state) { TimeoutHelper.ThrowIfNegativeArgument(timeout); return WorkflowApplication.BeginInvoke(workflow, null, extensions, timeout, null, null, callback, state); } void CancelCallback(IAsyncResult result) { if (result.CompletedSynchronously) { return; } AsyncInvokeContext context = (AsyncInvokeContext)result.AsyncState; // cancel only throws TimeoutException and shouldnt throw at all if timeout is infinite context.WorkflowApplication.EndCancel(result); } void InternalInvokeAsync(IDictionary inputs, TimeSpan timeout, object userState) { AsyncInvokeContext context = new AsyncInvokeContext(userState, this); if (userState != null) { AddToPendingInvokes(context); } Exception error = null; bool completedSynchronously = false; try { if (invokeCallback == null) { invokeCallback = Fx.ThunkCallback(new AsyncCallback(InvokeCallback)); } context.Operation.OperationStarted(); IAsyncResult result = WorkflowApplication.BeginInvoke(this.workflow, inputs, this.extensions, timeout, SynchronizationContext.Current, context, invokeCallback, context); if (result.CompletedSynchronously) { context.Outputs = this.EndInvoke(result); completedSynchronously = true; } } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } error = e; } if (error != null || completedSynchronously) { PostInvokeCompletedAndRemove(context, error); } } void InvokeCallback(IAsyncResult result) { if (result.CompletedSynchronously) { return; } AsyncInvokeContext context = (AsyncInvokeContext)result.AsyncState; WorkflowInvoker thisPtr = context.Invoker; Exception error = null; try { context.Outputs = thisPtr.EndInvoke(result); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } error = e; } thisPtr.PostInvokeCompletedAndRemove(context, error); } void PostInvokeCompleted(AsyncInvokeContext context, Exception error) { bool cancelled; if (error == null) { context.WorkflowApplication.GetCompletionStatus(out error, out cancelled); } else { cancelled = false; } PostInvokeCompleted(context, cancelled, error); } void PostInvokeCompleted(AsyncInvokeContext context, bool cancelled, Exception error) { InvokeCompletedEventArgs e = new InvokeCompletedEventArgs(error, cancelled, context); if (this.InvokeCompleted == null) { context.Operation.OperationCompleted(); } else { context.Operation.PostOperationCompleted(this.RaiseInvokeCompletedCallback, e); } } void PostInvokeCompletedAndRemove(AsyncInvokeContext context, Exception error) { if (context.UserState != null) { RemoveFromPendingInvokes(context.UserState); } PostInvokeCompleted(context, error); } void RaiseInvokeCompleted(object state) { EventHandler handler = this.InvokeCompleted; if (handler != null) { handler(this, (InvokeCompletedEventArgs)state); } } AsyncInvokeContext RemoveFromPendingInvokes(object userState) { AsyncInvokeContext context; lock (ThisLock) { if (this.PendingInvokes.TryGetValue(userState, out context)) { this.PendingInvokes.Remove(userState); } } return context; } } }