Utilisation de WorkflowInvoker et WorkflowApplication

Windows Workflow Foundation (WF) fournit plusieurs méthodes d’hébergement de workflow. WorkflowInvoker offre un moyen simple pour appeler un workflow comme s'il s'agissait d'un appel de méthode et ne peut être utilisé que pour les workflows qui n'utilisent pas la persistance. WorkflowApplication fournit un modèle plus riche pour exécuter des workflows, qui inclut la notification des événements de cycle de vie, le contrôle d'exécution, la modification de signet et la persistance. WorkflowServiceHost fournit la prise en charge des activités de messagerie et est principalement utilisé avec les services de workflow. Cette rubrique vous présente l'hébergement de workflow avec WorkflowInvoker et WorkflowApplication. Pour plus d’informations sur l’hébergement de workflow avec WorkflowServiceHost, consultez Services de workflow et Vue d’ensemble de l’hébergement des services de workflow.

Utilisation de WorkflowInvoker

WorkflowInvoker fournit un modèle pour l'exécution d'un workflow comme s'il s'agissait d'un appel de méthode. Pour appeler un workflow à l'aide de WorkflowInvoker, appelez la méthode Invoke et passez la définition du workflow à appeler. Dans cet exemple, une activité WriteLine est appelée à l'aide de WorkflowInvoker.

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

WorkflowInvoker.Invoke(wf);

Lorsqu'un workflow est appelé à l'aide de WorkflowInvoker, le workflow est exécuté sur le thread appelant et la méthode Invoke est bloquée jusqu'à ce que l'exécution du workflow soit terminée, durées d'inactivité comprises. Pour configurer un intervalle de délai d'attente au cours duquel le workflow doit être exécuté, utilisez l'une des surcharges de Invoke acceptant un paramètre TimeSpan. Dans cet exemple, un workflow est appelé deux fois avec deux intervalles de délai d'attente différents. Le premier workflow se termine, mais pas le second.

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

Notes

TimeoutException est levée uniquement si l'intervalle de délai d'attente est écoulé et que le workflow devient inactif pendant l'exécution. Un flux de travail dont le délai d'exécution dépasse l'intervalle de délai d'attente spécifié se termine correctement s'il ne devient pas inactif.

WorkflowInvoker fournit également des versions asynchrones de la méthode invoke. Pour plus d’informations, consultez InvokeAsync et BeginInvoke.

Définition des arguments d’entrée d’un workflow

Les données peuvent être passées dans un workflow à l’aide d’un dictionnaire de paramètres d’entrée, indexés par nom d’argument, qui correspondent aux arguments d’entrée du workflow. Dans cet exemple, un WriteLine est appelé et la valeur de son argument Text est spécifiée à l’aide du dictionnaire de paramètres d’entrée.

Activity wf = new WriteLine();

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

WorkflowInvoker.Invoke(wf, inputs);

Récupération des arguments de sortie d’un workflow

Les paramètres de sortie d'un workflow peuvent être obtenus à l'aide du dictionnaire de sorties retourné à partir de l'appel à Invoke. L’exemple suivant appelle un workflow composé d’une activité Divide unique qui a deux arguments d’entrée et deux arguments de sortie. Lorsque le workflow est appelé, le dictionnaire d'arguments (arguments) est passé ; il contient les valeurs de chaque argument d'entrée, indexées par nom d'argument. Lors du retour de l'appel à Invoke, chaque argument de sortie, également indexé par nom d'argument, est retourné dans le dictionnaire outputs.

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

Si le workflow dérive de ActivityWithResult, par exemple CodeActivity<TResult> ou Activity<TResult>, et qu’il existe des arguments de sortie en plus des arguments de sortie Result correctement définis, une surcharge non générique de Invoke doit être utilisée pour récupérer les arguments supplémentaires. Pour ce faire, la définition de workflow passée dans Invoke doit être de type Activity. Dans cet exemple l'activité Divide dérive de CodeActivity<int>, mais est déclarée comme Activity afin qu'une surcharge non générique de Invoke soit utilisée, qui retourne un dictionnaire d'arguments au lieu d'une valeur de retour unique.

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

Utilisation de WorkflowApplication

WorkflowApplication fournit un ensemble complet de fonctionnalités pour la gestion d'instance du workflow. WorkflowApplication joue le rôle d’un proxy thread-safe pour le WorkflowInstance réel, qui wrappe le runtime, et fournit des méthodes pour la création et le chargement d’instances de workflow, la mise en pause et la reprise, l’arrêt et la notification des événements de cycle de vie. Pour exécuter un workflow à l'aide de WorkflowApplication, créez le WorkflowApplication, abonnez-vous à tous les événements de cycle de vie souhaités, démarrez le workflow, puis attendez la fin de son exécution. Dans cet exemple, une définition de workflow constituée d'une activité WriteLine est créée et un WorkflowApplication est créé en utilisant la définition spécifiée de workflow. Completed est géré afin que l'hôte soit informé lorsque le workflow se termine, le workflow démarre par un appel à Run, puis l'hôte attend que le workflow se termine. Lorsque le workflow est terminé, le AutoResetEvent est défini et l'application hôte peut reprendre l'exécution, comme indiqué dans l'exemple suivant.

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

Événements de cycle de vie WorkflowApplication

En plus de Completed, les auteurs d'hôte peuvent être avertis lorsqu'un workflow est déchargé (Unloaded), est abandonné (Aborted), devient inactif (Idle et PersistableIdle), ou qu'une exception non gérée se produit (OnUnhandledException). Les développeurs d'applications de workflow peuvent gérer ces notifications et agir en conséquence, comme indiqué dans l'exemple suivant.

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

Définition des arguments d’entrée d’un workflow

Les données peuvent être passées dans un workflow comme il est démarré à l'aide d'un dictionnaire de paramètres, de la même façon que les données sont passées lors de l'utilisation de WorkflowInvoker. Chaque élément dans le dictionnaire correspond à un argument d’entrée du workflow spécifié. Dans cet exemple, un workflow qui consiste en une activité WriteLine est appelé et son argument Text est spécifié à l’aide du dictionnaire de paramètres d’entrée.

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

Récupération des arguments de sortie d’un workflow

Lorsque l’exécution d’un workflow est terminée, tous les arguments de sortie peuvent être récupérés dans le gestionnaire Completed en accédant au dictionnaire WorkflowApplicationCompletedEventArgs.Outputs. L'exemple suivant héberge un workflow à l'aide de WorkflowApplication. Une instance WorkflowApplication est construite avec une définition de workflow qui se compose d’une seule activité DiceRoll. L’activité DiceRoll a deux arguments de sortie qui représentent les résultats du jet de dés. Lorsque le workflow se termine, les sorties sont récupérées dans le gestionnaire 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();

Notes

WorkflowApplication et WorkflowInvoker prennent un dictionnaire d'arguments d'entrée et retournent un dictionnaire d'arguments out. Ces paramètres de dictionnaire, propriétés et valeurs de retour sont de type IDictionary<string, object>. L'instance réelle de la classe de dictionnaire qui est passée peut être toute classe qui implémente IDictionary<string, object>. Dans ces exemples, Dictionary<string, object> est utilisé. Pour plus d’informations sur les dictionnaires, consultez IDictionary<TKey,TValue> et Dictionary<TKey,TValue>.

Passage de données dans un workflow en cours d'exécution à l'aide de signets

Les signets sont le mécanisme par lequel une activité peut attendre passivement d'être reprise et un mécanisme pour le passage de données dans une instance de workflow en cours d'exécution. Si une activité attend des données, elle peut créer un Bookmark et inscrire une méthode de rappel à appeler lorsque le Bookmark est repris, comme indiqué dans l'exemple suivant.

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

Lors de l'exécution, l'activité ReadLine crée un Bookmark, inscrit un rappel, puis attend la reprise du Bookmark. Lors de la reprise, l'activité ReadLine affecte les données passées avec le Bookmark à son argument Result. Dans cet exemple, un workflow qui utilise l'activité ReadLine pour rassembler le nom de l'utilisateur et l'afficher dans la fenêtre de console est créé.

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

Lorsque l'activité ReadLine est exécutée, elle crée un Bookmark nommé UserName, puis attend la reprise du signet. L'hôte recueille les données voulues, puis reprend le Bookmark. Le workflow reprend, affiche le nom, puis se termine.

L'application hôte peut inspecter le workflow pour déterminer la présence de signets actifs. Pour ce faire, elle peut appeler la méthode GetBookmarks d'une instance WorkflowApplication ou inspecter le WorkflowApplicationIdleEventArgs dans le gestionnaire Idle.

L'exemple de code suivant est semblable à l'exemple précédent, mais les signets actifs sont énumérés avant la reprise du signet. Le workflow démarre et, une fois que Bookmark est créé et que le workflow est inactif, la méthode GetBookmarks est appelée. Lorsque le flux de travail est terminé, la sortie suivante s'affiche sur la console.

Comment vous appelez-vous ?
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: {0} - OwnerDisplayName: {1}",
        info.BookmarkName, info.OwnerDisplayName);
}

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

L'exemple de code suivant inspecte l'objet WorkflowApplicationIdleEventArgs passé dans le gestionnaire Idle d'une instance de WorkflowApplication. Dans cet exemple, le workflow qui devient inactif comporte un Bookmark portant le nom EnterGuess et appartenant à une activité nommée ReadInt. Cet exemple de code est basé sur Comment exécuter un workflow, qui fait partie du tutoriel de démarrage. Si le gestionnaire Idle utilisé dans cette étape est modifié de façon à contenir le code de cet exemple, la sortie suivante s'affiche.

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

Résumé

WorkflowInvoker offre un moyen simplifié d'appeler des workflows et, bien qu'il fournisse des méthodes pour le passage des données au début d'un workflow et l'extraction de données d'un workflow terminé, il ne prévoit pas de scénarios plus complexes dans lesquels WorkflowApplication peut être utilisé.