Condividi tramite


Utilizzo di WorkflowInvoker e WorkflowApplication

Windows Workflow Foundation offre diversi metodi per l'hosting dei flussi di lavoro. WorkflowInvoker offre un modo semplice per richiamare un flusso di lavoro come se fosse una chiamata a un metodo e può essere usato solo per i flussi di lavoro che non usano la persistenza. WorkflowApplication fornisce un modello più dettagliato per l'esecuzione di flussi di lavoro che include la notifica degli eventi del ciclo di vita, il controllo di esecuzione, la ripresa del segnalibro e la persistenza. WorkflowServiceHost fornisce il supporto per le attività di messaggistica e viene principalmente usato con i servizi flusso di lavoro. In questo argomento viene illustrato l'hosting del flusso di lavoro con WorkflowInvoker e WorkflowApplication. Per altre informazioni sull'hosting di flussi di lavoro con WorkflowServiceHost, vedere Servizi flusso di lavoro e Panoramica dei servizi flusso di lavoro.

Uso di WorkflowInvoker

L'oggetto WorkflowInvoker fornisce un modello per l'esecuzione di un flusso di lavoro come se si trattasse di una chiamata al metodo. Per richiamare un flusso di lavoro tramite l'oggetto WorkflowInvoker, chiamare il metodo Invoke e passare la definizione del flusso di lavoro da richiamare. In questo esempio viene richiamata un'attività WriteLine tramite l'oggetto WorkflowInvoker.

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

WorkflowInvoker.Invoke(wf);

Quando un flusso di lavoro viene richiamato usando l'oggetto WorkflowInvoker, tale flusso di lavoro viene eseguito sul thread chiamante e il metodo Invoke viene bloccato finché il flusso di lavoro non viene completato, incluso qualsiasi tempo di inattività. Per configurare un intervallo di timeout entro cui completare il flusso di lavoro, usare uno degli overload Invoke che accetta un parametro TimeSpan. In questo esempio un flusso di lavoro viene richiamato due volte con due intervalli di timeout diversi. Il primo flusso di lavoro viene completato, ma non il secondo.

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

L'eccezione TimeoutException viene generata solo se l'intervallo di timeout scade e il flusso di lavoro diventa inattivo durante l'esecuzione. Un flusso di lavoro il cui completamento richiede più tempo rispetto all'intervallo di timeout specificato viene completato correttamente se non diventa inattivo.

L'elemento WorkflowInvoker fornisce anche versioni asincrone del metodo di richiamo. Per altre informazioni, vedere InvokeAsync e BeginInvoke.

Impostazione degli argomenti di input di un flusso di lavoro

I dati possono essere passati in un flusso di lavoro tramite un dizionario di parametri di input, con chiavi in base al nome dell'argomento, che sono mappati agli argomenti di input del flusso di lavoro. In questo esempio viene richiamato un oggetto WriteLine e il valore del relativo argomento Text viene specificato usando il dizionario di parametri di input.

Activity wf = new WriteLine();

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

WorkflowInvoker.Invoke(wf, inputs);

Recupero degli argomenti di output di un flusso di lavoro

I parametri di output di un flusso di lavoro possono essere ottenuti tramite il dizionario di output restituito dalla chiamata all'overload Invoke. Nell'esempio seguente viene richiamato un flusso di lavoro composto da una singola attività Divide che dispone di due argomenti di input e due di output. Quando viene richiamato il flusso di lavoro, viene passato il dizionario arguments che contiene i valori per ogni argomento di input, con chiavi in base al nome dell'argomento. Quando la chiamata a Invoke restituisce un valore, ogni argomento di output viene restituito nel dizionario outputs, anche con chiavi in base al nome dell'argomento.

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("{0} / {1} = {2} Remainder {3}",
    dividend, divisor, outputs["Result"], outputs["Remainder"]);

Se il flusso di lavoro deriva da ActivityWithResult, ad esempio CodeActivity<TResult> o Activity<TResult>, ed esistono altri argomenti di output oltre all'argomento di output Result definito correttamente, è necessario usare un overload non generico di Invoke per recuperare gli argomenti aggiuntivi. A tale scopo, la definizione del flusso di lavoro passata in Invoke deve essere di tipo Activity. In questo esempio l'attività Divide deriva da CodeActivity<int>, ma viene dichiarata come Activity in modo che venga usato un overload non generico di Invoke che restituisce un dizionario di argomenti anziché un solo valore restituito.

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("{0} / {1} = {2} Remainder {3}",
    dividend, divisor, outputs["Result"], outputs["Remainder"]);

Uso di WorkflowApplication

WorkflowApplication offre un'ampia gamma di funzionalità per la gestione delle istanze del flusso di lavoro. WorkflowApplication agisce da proxy thread-safe verso l'oggetto WorkflowInstance effettivo, che incapsula il runtime e fornisce i metodo per creare e caricare istanze del flusso di lavoro, sospendere e riprendere, chiudere e inviare notifiche di eventi del ciclo di vita. Per eseguire un flusso di lavoro tramite l'oggetto WorkflowApplication è necessario creare l'oggetto WorkflowApplication, sottoscrivere un qualsiasi evento del ciclo di vita desiderato, avviare il flusso di lavoro, quindi attendere il relativo completamento. In questo esempio viene creata una definizione del flusso di lavoro costituita da un'attività WriteLine; con la definizione del flusso specificata viene creato poi un oggetto WorkflowApplication. L'oggetto Completed viene gestito in modo che all'host sia notificato quando il flusso di lavoro viene completato, il flusso di lavoro viene avviato con una chiamata a Run, quindi l'host attende il completamento del flusso di lavoro. Quando il flusso di lavoro viene completato, l'oggetto AutoResetEvent viene impostato e l'applicazione host può riprendere l'esecuzione, come mostrato nell'esempio seguente.

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

Eventi del ciclo di vita di WorkflowApplication

Oltre alla proprietà Completed, gli autori di host possono ricevere una notifica quando un flusso di lavoro viene scaricato (Unloaded), interrotto (Aborted), diventa inattivo (Idle e PersistableIdle) o si verifica un'eccezione non gestita (OnUnhandledException). Gli sviluppatori di applicazioni del flusso di lavoro possono gestire queste notifiche e intraprendere le azioni appropriate, come mostrato nell'esempio seguente.

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

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

wfApp.Aborted = delegate (WorkflowApplicationAbortedEventArgs e)
{
    // Display the exception that caused the workflow
    // to abort.
    Console.WriteLine("Workflow {0} Aborted.", e.InstanceId);
    Console.WriteLine("Exception: {0}\n{1}",
        e.Reason.GetType().FullName,
        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 {0} Idle.", e.InstanceId);
};

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 {0} Unloaded.", e.InstanceId);
};

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

    Console.WriteLine("ExceptionSource: {0} - {1}",
        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;
};

Impostazione degli argomenti di input di un flusso di lavoro

I dati possono essere passati in un flusso di lavoro come se avviati tramite un dizionario di parametri, ovvero in modo simile al passaggio dei dati tramite l'oggetto WorkflowInvoker. Ogni elemento del dizionario viene mappato a un argomento di input del flusso di lavoro specificato. In questo esempio viene richiamato un flusso di lavoro costituito da un'attività WriteLine e il relativo argomento Text viene specificato usando il dizionario di parametri di input.

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

Recupero degli argomenti di output di un flusso di lavoro

Quando un flusso di lavoro viene completato, qualsiasi argomento di output può essere recuperato nel gestore Completed eseguendo l'accesso al dizionario WorkflowApplicationCompletedEventArgs.Outputs. Nell'esempio seguente viene ospitato un flusso di lavoro tramite WorkflowApplication. Un'istanza di WorkflowApplication viene costruita usando una definizione di flusso di lavoro composta da una singola attività DiceRoll. L'attività DiceRoll dispone di due argomenti di output che rappresentano i risultati dell'operazione di lancio dei dadi. Quando il flusso di lavoro viene completato, gli output vengono recuperati nel gestore 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 {0} Terminated.", e.InstanceId);
        Console.WriteLine("Exception: {0}\n{1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine("Workflow {0} Canceled.", e.InstanceId);
    }
    else
    {
        Console.WriteLine("Workflow {0} Completed.", e.InstanceId);

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

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

Nota

Gli oggetti WorkflowApplication e WorkflowInvoker accettano un dizionario di argomenti di input e restituiscono un dizionario di argomenti out. Questi parametri, proprietà e valori restituiti del dizionario sono di tipo IDictionary<string, object>. L'istanza effettiva della classe di dizionario passata può essere qualsiasi classe che implementa IDictionary<string, object>. In questi esempi, viene usato Dictionary<string, object>. Per altre informazioni sui dizionari, vedere IDictionary<TKey,TValue> e Dictionary<TKey,TValue>.

Passaggio di dati in un flusso di lavoro in esecuzione tramite i segnalibri

I segnalibri consentono a un'attività di attendere passivamente la relativa ripresa, nonché di passare i dati a un'istanza del flusso di lavoro in esecuzione. Se un'attività sta attendendo dei dati, può creare un oggetto Bookmark e registrare un metodo callback da chiamare quando l'oggetto Bookmark viene ripreso, come mostrato nell'esempio seguente.

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

In caso di esecuzione, l'attività ReadLine crea un oggetto Bookmark, registra un callback, quindi attende la ripresa dell'oggetto Bookmark. Una volta ripresa, l'attività ReadLine assegna i dati passati con l'oggetto Bookmark al relativo argomento Result. In questo esempio viene creato un flusso di lavoro che usa l'attività ReadLine per rilevare il nome dell'utente e visualizzarlo nella finestra della 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: {0}", result);

Quando l'attività ReadLine viene eseguita, crea un oggetto Bookmark denominato UserName e attende la ripresa del segnalibro. L'host raccoglie i dati desiderati, quindi riprende l'oggetto Bookmark. Il flusso di lavoro viene ripreso, ne viene visualizzato il nome, quindi viene completato.

L'applicazione host può esaminare il flusso di lavoro per determinare se sono disponibili segnalibri attivi. Tale operazione può essere eseguita chiamando il metodo GetBookmarks di un'istanza WorkflowApplication o esaminando l'oggetto WorkflowApplicationIdleEventArgs nel gestore Idle.

L'esempio di codice seguente è simile al precedente, ad eccezione del fatto che i segnalibri attivi vengono enumerati prima che venga ripreso il segnalibro. Il flusso di lavoro viene avviato e, una volta creato l'oggetto Bookmark e reso inattivo il flusso di lavoro, viene chiamato il metodo GetBookmarks. Quando il flusso di lavoro viene completato, l'output seguente viene visualizzato nella console.

Come ti chiami?
BookmarkName: UserName - OwnerDisplayName: ReadLineSteveCiao, 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: {0} - OwnerDisplayName: {1}",
        info.BookmarkName, info.OwnerDisplayName);
}

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

Nell'esempio di codice seguente viene esaminato l'oggetto WorkflowApplicationIdleEventArgs passato nel gestore Idle di un'istanza WorkflowApplication. In questo esempio il flusso di lavoro che diventa inattivo dispone di un oggetto Bookmark denominato EnterGuess, di proprietà di un'attività denominata ReadInt. Questo esempio di codice è basato su Procedura: eseguire un flusso di lavoro, che fa parte dell'Esercitazione introduttiva. Se il gestore Idle nel passaggio indicato viene modificato per contenere il codice da questo esempio, viene visualizzato l'output seguente.

BookmarkName: EnterGuess - OwnerDisplayName: ReadInt

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

    idleEvent.Set();
};

Riepilogo

L'oggetto WorkflowInvoker rappresenta un modo semplice per richiamare i flussi di lavoro e, sebbene fornisca i metodi per passare i dati all'inizio di un flusso di lavoro ed estrarli da un flusso di lavoro completato, non garantisce scenari più complessi in cui è possibile usare l'oggetto WorkflowApplication.