Partilhar via


Usando WorkflowInvoker e WorkflowApplication

O Windows Workflow Foundation (WF) fornece vários métodos de hospedagem de fluxos de trabalho. WorkflowInvoker Fornece uma maneira simples de invocar um fluxo de trabalho como se fosse uma chamada de método e pode ser usado apenas para fluxos de trabalho que não usam persistência. WorkflowApplication Fornece um modelo mais avançado para a execução de fluxos de trabalho que inclui notificação de eventos do ciclo de vida, controle de execução, retomada de marcadores e persistência. WorkflowServiceHost Fornece suporte para atividades de mensagens e é usado principalmente com serviços de fluxo de trabalho. Este tópico apresenta a hospedagem de fluxo de trabalho com WorkflowInvoker e WorkflowApplication. Para obter mais informações sobre como hospedar fluxos de trabalho com WorkflowServiceHost, consulte Serviços de Fluxo de Trabalho e Visão Geral dos Serviços de Hospedagem de Fluxo de Trabalho.

Usando WorkflowInvoker

WorkflowInvoker Fornece um modelo para executar um fluxo de trabalho como se fosse uma chamada de método. Para invocar um fluxo de trabalho usando WorkflowInvoker, chame o método Invoke e passe a definição do fluxo de trabalho a ser invocado. Neste exemplo, uma WriteLine atividade é invocada usando WorkflowInvoker.

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

WorkflowInvoker.Invoke(wf);

Quando um fluxo de trabalho é invocado usando WorkflowInvoker, o fluxo de trabalho é executado no thread de chamada, e o método Invoke é bloqueado até que o fluxo de trabalho seja concluído, incluindo qualquer tempo de ociosidade. Para configurar um intervalo de tempo limite no qual o fluxo de trabalho deve ser concluído, use uma das Invoke sobrecargas que aceitam um parâmetro TimeSpan. Neste exemplo, um fluxo de trabalho é invocado duas vezes com dois intervalos de tempo limite diferentes. O primeiro fluxo de trabalho é concluído, mas o segundo não.

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);
}

Observação

O TimeoutException só é lançado se o intervalo de tempo de espera decorrer e o fluxo de trabalho ficar ocioso durante a execução. Um fluxo de trabalho que leva mais tempo do que o intervalo de tempo limite especificado para ser concluído com êxito se o fluxo de trabalho não ficar ocioso.

WorkflowInvoker Também fornece versões assíncronas do método Invoke. Para obter mais informações, consulte InvokeAsync e BeginInvoke.

Definindo argumentos de entrada de um fluxo de trabalho

Os dados podem ser passados para um fluxo de trabalho usando um dicionário de parâmetros de entrada, indexados pelo nome do argumento, que correspondem aos argumentos de entrada do fluxo de trabalho. Neste exemplo, a WriteLine é invocada e o valor para o seu argumento Text é especificado usando o dicionário 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);

Recuperando argumentos de saída de um fluxo de trabalho

Os parâmetros de saída de um workflow podem ser obtidos ao usar o dicionário de saídas que é retornado da chamada para Invoke. O exemplo a seguir invoca um fluxo de trabalho que consiste em uma única Divide atividade que tem dois argumentos de entrada e dois argumentos de saída. Quando o fluxo de trabalho é invocado, é passado o arguments dicionário que contém os valores para cada argumento de entrada, digitado pelo nome do argumento. Quando a chamada para Invoke retorna, cada argumento de saída é retornado no dicionário outputs, também associado ao nome do 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"]}");

Se o fluxo de trabalho derivar de ActivityWithResult, tal como CodeActivity<TResult> ou Activity<TResult>, e houver argumentos de saída além do argumento de saída bem definido Result, uma sobrecarga não genérica de Invoke deverá ser usada para recuperar os argumentos adicionais. Para fazer isso, a definição de fluxo de trabalho passada para Invoke deve ser do tipo Activity. Neste exemplo, a atividade Divide deriva de CodeActivity<int>, mas é declarada como Activity para que seja usada uma sobrecarga não genérica de Invoke, que retorna um dicionário de argumentos em vez de um único valor de retorno.

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 do WorkflowApplication

WorkflowApplication Fornece um conjunto avançado de recursos para gerenciamento de instâncias de fluxo de trabalho. WorkflowApplication atua como um proxy seguro para threads para o real WorkflowInstance, que encapsula o tempo de execução e fornece métodos para criar e carregar instâncias de fluxo de trabalho, pausar e retomar, encerrar e notificar sobre eventos do ciclo de vida. Para executar um fluxo de trabalho usando WorkflowApplication, crie o WorkflowApplication, assine os eventos de ciclo de vida desejados, inicie o fluxo de trabalho e aguarde sua conclusão. Neste exemplo, uma definição de fluxo de trabalho que consiste em uma WriteLine atividade é criada e uma WorkflowApplication é criada usando a definição de fluxo de trabalho especificada. Completed é manipulado de forma que o host seja notificado quando o fluxo de trabalho estiver concluído; o fluxo de trabalho é iniciado com uma chamada para Run e, em seguida, o host aguarda a conclusão do fluxo de trabalho. Quando o fluxo de trabalho for concluído, o AutoResetEvent será definido e o aplicativo host poderá retomar a execução, conforme mostrado no exemplo a seguir.

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();

Fluxo de trabalhoEventos do ciclo de vida do aplicativo

Além de Completed, os autores do host podem ser notificados quando um fluxo de trabalho é descarregado (Unloaded), abortado (Aborted), fica inativo (Idle e PersistableIdle), ou ocorre uma exceção não tratada (OnUnhandledException). Os desenvolvedores de aplicativos de fluxo de trabalho podem lidar com essas notificações e tomar as medidas apropriadas, conforme mostrado no exemplo a seguir.

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;
};

Definindo argumentos de entrada de um fluxo de trabalho

Os dados podem ser passados para um fluxo de trabalho assim que é iniciado, usando um dicionário de parâmetros, semelhante à maneira como os dados são passados ao usar WorkflowInvoker. Cada item no dicionário é mapeado para um argumento de entrada do fluxo de trabalho especificado. Neste exemplo, um fluxo de trabalho que consiste em uma WriteLine atividade é invocado e seu Text argumento é especificado usando o dicionário 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();

Recuperando argumentos de saída de um fluxo de trabalho

Quando um fluxo de trabalho é concluído, quaisquer argumentos de saída podem ser recuperados pelo manipulador Completed acessando o dicionário WorkflowApplicationCompletedEventArgs.Outputs. O exemplo a seguir configura um fluxo de trabalho usando WorkflowApplication. Uma WorkflowApplication instância é construída usando uma definição de fluxo de trabalho que consiste em uma única DiceRoll atividade. A atividade DiceRoll tem dois argumentos de saída que representam os resultados da operação de rolagem de dados. Quando o fluxo de trabalho é concluído, as saídas são recuperadas no manipulador Completed.

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();

Observação

WorkflowApplication e WorkflowInvoker recebem um dicionário de argumentos de entrada e retornam um dicionário de out argumentos. Esses parâmetros de dicionário, propriedades e valores de retorno são do tipo IDictionary<string, object>. A instância real da classe de dicionário que é passada pode ser qualquer classe que implementa IDictionary<string, object>. Nestes exemplos, Dictionary<string, object> é usado. Para obter mais informações sobre dicionários, consulte IDictionary<TKey,TValue> e Dictionary<TKey,TValue>.

Passando dados para um fluxo de trabalho em execução usando marcadores

Os marcadores são o mecanismo pelo qual uma atividade pode esperar passivamente para ser retomada e são um mecanismo para passar dados para uma instância de fluxo de trabalho em execução. Se uma atividade estiver à espera de dados, poderá criar um Bookmark e registar um método de retorno de chamada que será acionado quando o Bookmark for retomado, conforme ilustrado no exemplo a seguir.

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);
    }

Quando executada, a ReadLine atividade cria um Bookmark, regista um retorno de chamada e depois aguarda que o Bookmark seja retomado. Quando é retomada, a ReadLine atividade atribui os dados que foram passados com o Bookmark ao seu Result argumento. Neste exemplo, é criado um fluxo de trabalho que usa a ReadLine atividade para coletar o nome do usuário e exibi-lo na janela do console.

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}");

Quando a ReadLine atividade é executada, ela cria um Bookmark chamado UserName e, em seguida, aguarda pela retoma do marcador. O host coleta os dados desejados e, em seguida, retoma o Bookmark. O fluxo de trabalho retoma, exibe o nome e depois conclui.

O aplicativo host pode inspecionar o fluxo de trabalho para determinar se há marcadores ativos. Ele pode fazer isso chamando o método GetBookmarks de uma instância WorkflowApplication ou inspecionando o WorkflowApplicationIdleEventArgs no manipulador Idle.

O exemplo de código a seguir é como o exemplo anterior, exceto que os marcadores ativos são enumerados antes que o indicador seja retomado. O fluxo de trabalho é iniciado e, uma vez que o Bookmark é criado e o fluxo de trabalho entra em estado de espera, GetBookmarks é chamado. Quando o fluxo de trabalho é concluído, a saída a seguir é exibida no console.

Qual é o teu nome?
BookmarkName: UserName - OwnerDisplayName: ReadLineSteveOlá, 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());

O exemplo de código seguinte inspeciona o WorkflowApplicationIdleEventArgs passado para o manipulador Idle de uma instância WorkflowApplication. Neste exemplo, o fluxo de trabalho ocioso tem um Bookmark com um nome de EnterGuess, de propriedade de uma atividade chamada ReadInt. Este exemplo de código é baseado em Como: Executar um fluxo de trabalho, que faz parte do Tutorial de introdução. Se o Idle manipulador nessa etapa for modificado para conter o código deste exemplo, a saída a seguir será exibida.

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();
};

Resumo

WorkflowInvoker fornece uma maneira leve de invocar fluxos de trabalho e, embora forneça métodos para passar dados no início de um fluxo de trabalho e extrair dados de um fluxo de trabalho concluído, ele não fornece cenários mais complexos, que é onde WorkflowApplication pode ser usado.