Compartir a través de


Uso de WorkflowInvoker y WorkflowApplication

Windows Workflow Foundation (WF) proporciona varios métodos de hospedaje de flujos de trabajo. WorkflowInvoker proporciona una manera sencilla de invocar un flujo de trabajo como si fuera una llamada de método y solo se puede usar para flujos de trabajo que no usan persistencia. WorkflowApplication proporciona un modelo más completo para ejecutar flujos de trabajo que incluyen notificaciones de eventos de ciclo de vida, control de ejecución, reanudación de marcadores y persistencia. WorkflowServiceHost proporciona compatibilidad con las actividades de mensajería y se usa principalmente con los servicios de flujo de trabajo. En este tema se presenta el hospedaje de flujos de trabajo con WorkflowInvoker y WorkflowApplication. Para obtener más información sobre cómo hospedar flujos de trabajo con WorkflowServiceHost, vea Workflow Services and Hosting Workflow Services Overview.

Uso de WorkflowInvoker

WorkflowInvoker proporciona un modelo para ejecutar un flujo de trabajo como si fuera una llamada de método. Para invocar un flujo de trabajo mediante WorkflowInvoker, llame al Invoke método y pase la definición de flujo de trabajo del flujo de trabajo que se va a invocar. En este ejemplo, se invoca una WriteLine actividad mediante WorkflowInvoker.

Activity wf = new WriteLine
{
    Text = "Hello World."
};

WorkflowInvoker.Invoke(wf);

Cuando se invoca un flujo de trabajo mediante WorkflowInvoker, el flujo de trabajo se ejecuta en el subproceso que realiza la llamada y el Invoke método se bloquea hasta que se complete el flujo de trabajo, incluido cualquier tiempo de inactividad. Para configurar un intervalo de tiempo de espera en el que se debe completar el flujo de trabajo, use una de las sobrecargas Invoke que tome un parámetro TimeSpan. En este ejemplo, se invoca un flujo de trabajo dos veces con dos intervalos de tiempo de espera diferentes. El primer flujo de trabajo se completa, pero el segundo no.

Activity wf = new Sequence()
{
    Activities =
    {
        new WriteLine()
        {
            Text = "Before the 1 minute delay."
        },
        new Delay()
        {
            Duration = TimeSpan.FromMinutes(1)
        },
        new WriteLine()
        {
            Text = "After the 1 minute delay."
        }
    }
};

// This workflow completes successfully.
WorkflowInvoker.Invoke(wf, TimeSpan.FromMinutes(2));

// This workflow does not complete and a TimeoutException
// is thrown.
try
{
    WorkflowInvoker.Invoke(wf, TimeSpan.FromSeconds(30));
}
catch (TimeoutException ex)
{
    Console.WriteLine(ex.Message);
}

Nota:

Solo se lanza TimeoutException si transcurre el intervalo de tiempo de espera y el flujo de trabajo se vuelve inactivo durante la ejecución. Un flujo de trabajo que tarda más que el intervalo de tiempo de espera especificado en completarse terminará correctamente si no se vuelve inactivo.

WorkflowInvoker también proporciona versiones asincrónicas del método invoke. Para obtener más información, vea InvokeAsync y BeginInvoke.

Establecer argumentos de entrada de un flujo de trabajo

Los datos se pueden introducir en un flujo de trabajo mediante un diccionario de parámetros de entrada, cuyo contenido está determinado por el nombre del argumento y que se asignan a los argumentos de entrada del flujo de trabajo. En este ejemplo, se invoca un WriteLine y el valor de su Text argumento se especifica mediante el diccionario de parámetros de entrada.

Activity wf = new WriteLine();

Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Text", "Hello World.");

WorkflowInvoker.Invoke(wf, inputs);

Recuperar argumentos de salida de un flujo de trabajo

Los parámetros de salida de un flujo de trabajo se pueden obtener mediante el diccionario de salidas que se devuelve de la llamada a Invoke. En el ejemplo siguiente se invoca un flujo de trabajo que consta de una sola Divide actividad que tiene dos argumentos de entrada y dos argumentos de salida. Cuando se invoca el flujo de trabajo, se pasa el arguments diccionario que contiene los valores de cada argumento de entrada, con clave por nombre de argumento. Cuando la llamada a Invoke devuelve resultados, cada uno de los argumentos de salida se devuelve en el diccionario outputs, ordenados por nombre de argumento.

public sealed class Divide : CodeActivity
{
    [RequiredArgument]
    public InArgument<int> Dividend { get; set; }

    [RequiredArgument]
    public InArgument<int> Divisor { get; set; }

    public OutArgument<int> Remainder { get; set; }
    public OutArgument<int> Result { get; set; }

    protected override void Execute(CodeActivityContext context)
    {
        int quotient = Dividend.Get(context) / Divisor.Get(context);
        int remainder = Dividend.Get(context) % Divisor.Get(context);

        Result.Set(context, quotient);
        Remainder.Set(context, remainder);
    }
}
int dividend = 500;
int divisor = 36;

Dictionary<string, object> arguments = new Dictionary<string, object>();
arguments.Add("Dividend", dividend);
arguments.Add("Divisor", divisor);

IDictionary<string, object> outputs =
    WorkflowInvoker.Invoke(new Divide(), arguments);

Console.WriteLine($"{dividend} / {divisor} = {outputs["Result"]} Remainder {outputs["Remainder"]}");

Si el flujo de trabajo se deriva de ActivityWithResult, como CodeActivity<TResult> o Activity<TResult>, y hay argumentos de salida además del argumento de salida bien definido Result , se debe usar una sobrecarga no genérica de Invoke para recuperar los argumentos adicionales. Para ello, la definición de flujo de trabajo pasada a Invoke debe ser de tipo Activity. En este ejemplo, la Divide actividad deriva de CodeActivity<int>, pero se declara como Activity para que se use una sobrecarga no genérica de Invoke , que devuelve un diccionario de argumentos en lugar de un único valor devuelto.

public sealed class Divide : CodeActivity<int>
{
    public InArgument<int> Dividend { get; set; }
    public InArgument<int> Divisor { get; set; }
    public OutArgument<int> Remainder { get; set; }

    protected override int Execute(CodeActivityContext context)
    {
        int quotient = Dividend.Get(context) / Divisor.Get(context);
        int remainder = Dividend.Get(context) % Divisor.Get(context);

        Remainder.Set(context, remainder);

        return quotient;
    }
}
int dividend = 500;
int divisor = 36;

Dictionary<string, object> arguments = new Dictionary<string, object>();
arguments.Add("Dividend", dividend);
arguments.Add("Divisor", divisor);

Activity wf = new Divide();

IDictionary<string, object> outputs =
    WorkflowInvoker.Invoke(wf, arguments);

Console.WriteLine($"{dividend} / {divisor} = {outputs["Result"]} Remainder {outputs["Remainder"]}");

Uso de WorkflowApplication

WorkflowApplication proporciona un amplio conjunto de características para la administración de instancias de flujo de trabajo. WorkflowApplication actúa como un proxy seguro para hilos al elemento real WorkflowInstance, que encapsula el tiempo de ejecución y proporciona métodos para la creación y carga de instancias de flujo de trabajo, pausa y reanudación, finalización y notificación de eventos del ciclo de vida. Para ejecutar un flujo de trabajo mediante WorkflowApplication, cree el WorkflowApplication, suscríbase a los eventos de ciclo de vida deseados, inicie el flujo de trabajo y espere a que finalice. En este ejemplo, se crea una definición de flujo de trabajo que consta de una WriteLine actividad y WorkflowApplication se crea mediante la definición de flujo de trabajo especificada. Completed se controla para que el host reciba una notificación cuando se complete el flujo de trabajo, el flujo de trabajo se inicia con una llamada a Runy, a continuación, el host espera a que se complete el flujo de trabajo. Cuando se completa el flujo de trabajo, AutoResetEvent se establece y la aplicación host puede reanudar la ejecución, como se muestra en el ejemplo siguiente.

AutoResetEvent syncEvent = new AutoResetEvent(false);

Activity wf = new WriteLine
{
    Text = "Hello World."
};

// Create the WorkflowApplication using the desired
// workflow definition.
WorkflowApplication wfApp = new WorkflowApplication(wf);

// Handle the desired lifecycle events.
wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
    syncEvent.Set();
};

// Start the workflow.
wfApp.Run();

// Wait for Completed to arrive and signal that
// the workflow is complete.
syncEvent.WaitOne();

Eventos del ciclo de vida de WorkflowApplication

Además de Completed, los autores del host pueden recibir notificaciones cuando se descarga un flujo de trabajo (Unloaded), se anula (Aborted), se convierte en inactivo (Idle y PersistableIdle), o se produce una excepción no controlada (OnUnhandledException). Los desarrolladores de aplicaciones de flujo de trabajo pueden controlar estas notificaciones y tomar las medidas adecuadas, como se muestra en el ejemplo siguiente.

wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine($"Workflow {e.InstanceId} Terminated.");
        Console.WriteLine($"Exception: {e.TerminationException.GetType().FullName}\n{e.TerminationException.Message}");
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine($"Workflow {e.InstanceId} Canceled.");
    }
    else
    {
        Console.WriteLine($"Workflow {e.InstanceId} Completed.");

        // Outputs can be retrieved from the Outputs dictionary,
        // keyed by argument name.
        // Console.WriteLine($"The winner is {e.Outputs["Winner"]}.");
    }
};

wfApp.Aborted = delegate (WorkflowApplicationAbortedEventArgs e)
{
    // Display the exception that caused the workflow
    // to abort.
    Console.WriteLine($"Workflow {e.InstanceId} Aborted.");
    Console.WriteLine($"Exception: {e.Reason.GetType().FullName}\n{e.Reason.Message}");
};

wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
    // Perform any processing that should occur
    // when a workflow goes idle. If the workflow can persist,
    // both Idle and PersistableIdle are called in that order.
    Console.WriteLine($"Workflow {e.InstanceId} Idle.");
};

wfApp.PersistableIdle = delegate (WorkflowApplicationIdleEventArgs e)
{
    // Instruct the runtime to persist and unload the workflow.
    // Choices are None, Persist, and Unload.
    return PersistableIdleAction.Unload;
};

wfApp.Unloaded = delegate (WorkflowApplicationEventArgs e)
{
    Console.WriteLine($"Workflow {e.InstanceId} Unloaded.");
};

wfApp.OnUnhandledException = delegate (WorkflowApplicationUnhandledExceptionEventArgs e)
{
    // Display the unhandled exception.
    Console.WriteLine($"OnUnhandledException in Workflow {e.InstanceId}\n{e.UnhandledException.Message}");

    Console.WriteLine($"ExceptionSource: {e.ExceptionSource.DisplayName} - {e.ExceptionSourceInstanceId}");

    // Instruct the runtime to terminate the workflow.
    // Other choices are Abort and Cancel. Terminate
    // is the default if no OnUnhandledException handler
    // is present.
    return UnhandledExceptionAction.Terminate;
};

Establecer argumentos de entrada de un flujo de trabajo

Los datos se pueden pasar a un flujo de trabajo, ya que se inicia mediante un diccionario de parámetros, de forma similar a la forma en que se pasan los datos al usar WorkflowInvoker. Cada elemento del diccionario se asigna a un argumento de entrada del flujo de trabajo especificado. En este ejemplo, se invoca un flujo de trabajo que consta de una WriteLine actividad y su Text argumento se especifica mediante el diccionario de parámetros de entrada.

AutoResetEvent syncEvent = new AutoResetEvent(false);

Activity wf = new WriteLine();

// Create the dictionary of input parameters.
Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Text", "Hello World!");

// Create the WorkflowApplication using the desired
// workflow definition and dictionary of input parameters.
WorkflowApplication wfApp = new WorkflowApplication(wf, inputs);

// Handle the desired lifecycle events.
wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
    syncEvent.Set();
};

// Start the workflow.
wfApp.Run();

// Wait for Completed to arrive and signal that
// the workflow is complete.
syncEvent.WaitOne();

Recuperar argumentos de salida de un flujo de trabajo

Cuando se completa un flujo de trabajo, los argumentos de salida se pueden recuperar en el Completed controlador accediendo al WorkflowApplicationCompletedEventArgs.Outputs diccionario. En el ejemplo siguiente se hospeda un flujo de trabajo mediante WorkflowApplication. Una WorkflowApplication instancia se construye mediante una definición de flujo de trabajo que consta de una sola DiceRoll actividad. La DiceRoll actividad tiene dos argumentos de salida que representan los resultados de la operación de lanzamiento de dados. Cuando se completa el flujo de trabajo, las salidas se recuperan en el Completed controlador.

public sealed class DiceRoll : CodeActivity
{
    public OutArgument<int> D1 { get; set; }
    public OutArgument<int> D2 { get; set; }

    static Random r = new Random();

    protected override void Execute(CodeActivityContext context)
    {
        D1.Set(context, r.Next(1, 7));
        D2.Set(context, r.Next(1, 7));
    }
}
// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(new DiceRoll());

// Subscribe to any desired workflow lifecycle events.
wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine($"Workflow {e.InstanceId} Terminated.");
        Console.WriteLine($"Exception: {e.TerminationException.GetType().FullName}\n{e.TerminationException.Message}");
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine($"Workflow {e.InstanceId} Canceled.");
    }
    else
    {
        Console.WriteLine($"Workflow {e.InstanceId} Completed.");

        // Outputs can be retrieved from the Outputs dictionary,
        // keyed by argument name.
        Console.WriteLine($"The two dice are {e.Outputs["D1"]} and {e.Outputs["D2"]}.");
    }
};

// Run the workflow.
wfApp.Run();

Nota:

WorkflowApplication y WorkflowInvoker toman un diccionario de argumentos de entrada y devuelven un diccionario de out argumentos. Estos parámetros de diccionario, propiedades y valores devueltos son de tipo IDictionary<string, object>. La instancia real de la clase de diccionario que se pasa puede ser cualquier clase que implemente IDictionary<string, object>. En estos ejemplos, Dictionary<string, object> se usa . Para obtener más información sobre los diccionarios, vea IDictionary<TKey,TValue> y Dictionary<TKey,TValue>.

Pasar datos a un flujo de trabajo en ejecución mediante marcadores

Los marcadores son el mecanismo por el que una actividad puede esperar pasivamente a reanudarse y son un mecanismo para pasar datos a una instancia de flujo de trabajo en ejecución. Si una actividad está esperando los datos, puede crear Bookmark y registrar un método de devolución de llamada que se va a llamar cuando se reanude Bookmark, tal y como se muestra en el siguiente ejemplo.

public sealed class ReadLine : NativeActivity<string>
{
    [RequiredArgument]
    public InArgument<string> BookmarkName { get; set; }

    protected override void Execute(NativeActivityContext context)
    {
        // Create a Bookmark and wait for it to be resumed.
        context.CreateBookmark(BookmarkName.Get(context),
            new BookmarkCallback(OnResumeBookmark));
    }

    // NativeActivity derived activities that do asynchronous operations by calling
    // one of the CreateBookmark overloads defined on System.Activities.NativeActivityContext
    // must override the CanInduceIdle property and return true.
    protected override bool CanInduceIdle
    {
        get { return true; }
    }

    public void OnResumeBookmark(NativeActivityContext context, Bookmark bookmark, object obj)
    {
        // When the Bookmark is resumed, assign its value to
        // the Result argument.
        Result.Set(context, (string)obj);
    }

Cuando se ejecuta, la actividad ReadLine crea un Bookmark, registra una devolución de llamada y, a continuación, espera a que se reanude Bookmark. Cuando se reanuda, la ReadLine actividad asigna los datos que se pasaron con el Bookmark a su argumento Result. En este ejemplo, se crea un flujo de trabajo que usa la ReadLine actividad para recopilar el nombre del usuario y mostrarlo en la ventana de la consola.

Variable<string> name = new Variable<string>();

Activity wf = new Sequence
{
    Variables = { name },
    Activities =
     {
         new WriteLine
         {
             Text = "What is your name?"
         },
         new ReadLine
         {
             BookmarkName = "UserName",
             Result = new OutArgument<string>(name)
         },
         new WriteLine
         {
             Text = new InArgument<string>((env) =>
                 ("Hello, " + name.Get(env)))
         }
     }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

// Workflow lifecycle events omitted except idle.
AutoResetEvent idleEvent = new AutoResetEvent(false);

wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
    idleEvent.Set();
};

// Run the workflow.
wfApp.Run();

// Wait for the workflow to go idle before gathering
// the user's input.
idleEvent.WaitOne();

// Gather the user's input and resume the bookmark.
// Bookmark resumption only occurs when the workflow
// is idle. If a call to ResumeBookmark is made and the workflow
// is not idle, ResumeBookmark blocks until the workflow becomes
// idle before resuming the bookmark.
BookmarkResumptionResult result = wfApp.ResumeBookmark("UserName",
    Console.ReadLine());

// Possible BookmarkResumptionResult values:
// Success, NotFound, or NotReady
Console.WriteLine($"BookmarkResumptionResult: {result}");

Cuando se ejecuta la actividad ReadLine, crea un Bookmark llamado UserName y, a continuación, espera a que se reanude el marcador. El host recopila los datos deseados y, a continuación, reanuda Bookmark. El flujo de trabajo se reanuda, muestra el nombre y, a continuación, se completa.

La aplicación host puede inspeccionar el flujo de trabajo para determinar si hay marcadores activos. Puede hacerlo llamando al método GetBookmarks de una instancia WorkflowApplication o inspeccionando el objeto WorkflowApplicationIdleEventArgs en el controlador Idle.

El ejemplo de código siguiente es similar al ejemplo anterior, excepto que los marcadores activos se enumeran antes de reanudar el marcador. Se inicia el flujo de trabajo y, una vez que se crea Bookmark y el flujo de trabajo queda inactivo, se llama a GetBookmarks. Cuando se completa el flujo de trabajo, se muestra la salida siguiente en la consola.

¿Cómo te llamas?
BookmarkName: UserName - OwnerDisplayName: ReadLineSteveHello, Steve

Variable<string> name = new Variable<string>();

Activity wf = new Sequence
{
    Variables = { name },
    Activities =
     {
         new WriteLine
         {
             Text = "What is your name?"
         },
         new ReadLine
         {
             BookmarkName = "UserName",
             Result = new OutArgument<string>(name)
         },
         new WriteLine
         {
             Text = new InArgument<string>((env) =>
                 ("Hello, " + name.Get(env)))
         }
     }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

// Workflow lifecycle events omitted except idle.
AutoResetEvent idleEvent = new AutoResetEvent(false);

wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
    // You can also inspect the bookmarks from the Idle handler
    // using e.Bookmarks

    idleEvent.Set();
};

// Run the workflow.
wfApp.Run();

// Wait for the workflow to go idle and give it a chance
// to create the Bookmark.
idleEvent.WaitOne();

// Inspect the bookmarks
foreach (BookmarkInfo info in wfApp.GetBookmarks())
{
    Console.WriteLine($"BookmarkName: {info.BookmarkName} - OwnerDisplayName: {info.OwnerDisplayName}");
}

// Gather the user's input and resume the bookmark.
wfApp.ResumeBookmark("UserName", Console.ReadLine());

El siguiente ejemplo de código inspecciona el objeto WorkflowApplicationIdleEventArgs pasado al controlador de la propiedad Idle de una instancia de WorkflowApplication. En este ejemplo, el flujo de trabajo que se queda inactivo tiene uno Bookmark con el nombre EnterGuess, propiedad de una actividad denominada ReadInt. Este ejemplo de código se basa en Cómo: Ejecutar un flujo de trabajo, que forma parte del Tutorial de introducción. Si el Idle controlador de ese paso se modifica para contener el código de este ejemplo, se muestra la salida siguiente.

BookmarkName: EnterGuess - OwnerDisplayName: ReadInt

wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
    foreach (BookmarkInfo info in e.Bookmarks)
    {
        Console.WriteLine($"BookmarkName: {info.BookmarkName} - OwnerDisplayName: {info.OwnerDisplayName}");
    }

    idleEvent.Set();
};

Resumen

WorkflowInvoker proporciona una manera ligera de invocar flujos de trabajo y, aunque proporciona métodos para pasar datos al principio de un flujo de trabajo y extraer datos de un flujo de trabajo completado, no proporciona escenarios más complejos en los que se WorkflowApplication puede usar.