e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
511 lines
18 KiB
C#
511 lines
18 KiB
C#
//-----------------------------------------------------------------------------
|
|
// 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<object, AsyncInvokeContext> 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<InvokeCompletedEventArgs> InvokeCompleted;
|
|
|
|
public WorkflowInstanceExtensionManager Extensions
|
|
{
|
|
get
|
|
{
|
|
if (this.extensions == null)
|
|
{
|
|
this.extensions = new WorkflowInstanceExtensionManager();
|
|
}
|
|
|
|
return this.extensions;
|
|
}
|
|
}
|
|
|
|
Dictionary<object, AsyncInvokeContext> PendingInvokes
|
|
{
|
|
get
|
|
{
|
|
if (this.pendingInvokes == null)
|
|
{
|
|
this.pendingInvokes = new Dictionary<object, AsyncInvokeContext>();
|
|
}
|
|
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<string, object> Invoke(Activity workflow)
|
|
{
|
|
return Invoke(workflow, ActivityDefaults.InvokeTimeout);
|
|
}
|
|
|
|
[Fx.Tag.InheritThrows(From = "Invoke")]
|
|
public static IDictionary<string, object> Invoke(Activity workflow, TimeSpan timeout)
|
|
{
|
|
return Invoke(workflow, timeout, null);
|
|
}
|
|
|
|
[Fx.Tag.InheritThrows(From = "Invoke")]
|
|
public static IDictionary<string, object> Invoke(Activity workflow, IDictionary<string, object> inputs)
|
|
{
|
|
return Invoke(workflow, inputs, ActivityDefaults.InvokeTimeout, null);
|
|
}
|
|
|
|
[Fx.Tag.InheritThrows(From = "Invoke")]
|
|
public static IDictionary<string, object> Invoke(Activity workflow, IDictionary<string, object> 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<TResult>(Activity<TResult> 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<TResult>(Activity<TResult> workflow, IDictionary<string, object> 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<TResult>(Activity<TResult> workflow, IDictionary<string, object> inputs, TimeSpan timeout)
|
|
{
|
|
IDictionary<string, object> 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<TResult>(Activity<TResult> workflow, IDictionary<string, object> inputs, out IDictionary<string, object> 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<TResult> 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<string, object> 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<string, object> 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<string, object> EndInvoke(IAsyncResult result)
|
|
{
|
|
return WorkflowApplication.EndInvoke(result);
|
|
}
|
|
|
|
[Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")]
|
|
public IDictionary<string, object> Invoke()
|
|
{
|
|
return WorkflowInvoker.Invoke(this.workflow, ActivityDefaults.InvokeTimeout, this.extensions);
|
|
}
|
|
|
|
[Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")]
|
|
public IDictionary<string, object> 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<string, object> Invoke(IDictionary<string, object> inputs)
|
|
{
|
|
return WorkflowInvoker.Invoke(this.workflow, inputs, ActivityDefaults.InvokeTimeout, this.extensions);
|
|
}
|
|
|
|
[Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")]
|
|
public IDictionary<string, object> Invoke(IDictionary<string, object> 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<string, object> inputs)
|
|
{
|
|
InvokeAsync(inputs, null);
|
|
}
|
|
|
|
public void InvokeAsync(IDictionary<string, object> inputs, TimeSpan timeout)
|
|
{
|
|
InvokeAsync(inputs, timeout, null);
|
|
}
|
|
|
|
public void InvokeAsync(IDictionary<string, object> inputs, object userState)
|
|
{
|
|
InvokeAsync(inputs, ActivityDefaults.InvokeTimeout, userState);
|
|
}
|
|
|
|
public void InvokeAsync(IDictionary<string, object> 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<string, object> Invoke(Activity workflow, TimeSpan timeout, WorkflowInstanceExtensionManager extensions)
|
|
{
|
|
if (workflow == null)
|
|
{
|
|
throw FxTrace.Exception.ArgumentNull("workflow");
|
|
}
|
|
|
|
TimeoutHelper.ThrowIfNegativeArgument(timeout);
|
|
|
|
IDictionary<string, object> 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<string, object> Invoke(Activity workflow, IDictionary<string, object> 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<string, object> 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<string, object> 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<string, object> 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<InvokeCompletedEventArgs> 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;
|
|
}
|
|
}
|
|
}
|