Verwenden von WorkflowInvoker und WorkflowApplication

Windows Workflow Foundation (WF) bietet mehrere Methoden zum Hosten von Workflows. WorkflowInvoker stellt eine einfache Möglichkeit zum Aufrufen eines Workflows bereit, so als handelte es sich um einen Methodenaufruf, und kann nur für Workflows verwendet werden, die keine Persistenz verwenden. WorkflowApplication bietet ein umfangreicheres Modell zum Ausführen von Workflows, die Benachrichtigungen über Lebenszyklusereignisse, Ausführungssteuerung, Wiederaufnahme von Lesezeichen und Persistenz enthalten. WorkflowServiceHost umfasst die Unterstützung von Messagingaktivitäten und wird hauptsächlich in Verbindung mit Workflowdiensten verwendet. In diesem Thema wird das Workflowhosting mit WorkflowInvoker und WorkflowApplication erläutert. For more information about hosting workflows with WorkflowServiceHost, see Workflow Services and Hosting Workflow Services Overview.

Verwenden von WorkflowInvoker

WorkflowInvoker bietet ein Modell zum Ausführen eines Workflows, als ob es sich dabei um einen Methodenaufruf handeln würde. Um einen Workflow mit WorkflowInvoker aufzurufen, rufen Sie die Invoke-Methode auf, und übergeben Sie die Workflowdefinition des Workflows, der aufgerufen werden soll. In diesem Beispiel wird eine WriteLine-Aktivität mit dem WorkflowInvoker-Objekt aufgerufen.

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

WorkflowInvoker.Invoke(wf);

Beim Aufruf eines Workflow mit dem WorkflowInvoker-Objekt wird der Workflow für den aufrufenden Thread ausgeführt, und die Invoke-Methode wird blockiert, bis der Workflow abgeschlossen ist, einschließlich der eventuell anfallenden Leerlaufzeit. Um ein anderes Timeoutintervall für den Abschluss des Workflows zu konfigurieren, verwenden Sie eine der Invoke-Überladungen, die einen TimeSpan-Parameter annehmen. In diesem Beispiel wird ein Workflow mit zwei verschiedenen Timeoutintervallen zweimal aufgerufen. Der erste Workflow wird abgeschlossen, der zweite jedoch nicht.

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

Hinweis

Das TimeoutException-Objekt wird nur ausgelöst, wenn das Timeoutintervall verstreicht und der Workflow während der Ausführung in den Leerlauf wechselt. Ein Workflow, der erst nach dem angegebenen Timeoutintervall abgeschlossen wird, wird dennoch erfolgreich beendet, wenn der Workflow nicht in den Leerlauf wechselt.

WorkflowInvoker stellt auch asynchrone Versionen der Aufrufmethode bereit. Weitere Informationen finden Sie unter InvokeAsync und BeginInvoke.

Festlegen der Eingabeargumente eines Workflows

Daten können mit einem Wörterbuch von Eingabeparametern, die nach Argumentname sortiert sind und so den Eingabeargumenten des Workflows zugeordnet werden können, in einen Workflow übergeben werden. In diesem Beispiel wird ein WriteLine-Objekt aufgerufen, und der Wert für das zugehörige Text-Argument wird mit dem Wörterbuch von Eingabeparametern angegeben.

Activity wf = new WriteLine();

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

WorkflowInvoker.Invoke(wf, inputs);

Abrufen der Ausgabeargumente eines Workflows

Die Ausgabeparameter eines Workflows können mit dem Ausgabewörterbuch abgerufen werden. Es wird über den Aufruf von Invoke zurückgegeben. Im folgenden Beispiel wird ein Workflow aufgerufen, der aus einer einzelnen Divide-Aktivität besteht, die über zwei Eingabeargumente und zwei Ausgabeargumente verfügt. Beim Aufrufen des Workflows wird das arguments-Wörterbuch übergeben, das die Werte für die einzelnen Eingabeargumente sortiert nach Argumentname enthält. Wenn der Aufruf von Invoke zurückgegeben wird, werden die einzelnen Ausgabeargumente nach Argumentname sortiert im outputs-Wörterbuch zurückgegeben.

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

Wenn der Workflow von ActivityWithResult abgeleitet wird, wie z. B. CodeActivity<TResult> oder Activity<TResult>, und Ausgabeargumente zusätzlich zum gut definierten Result-Ausgabeargument vorhanden sind, muss eine nicht generische Überladung von Invoke verwendet werden, um die zusätzlichen Argumente abzurufen. Hierzu muss die an Invoke übergebene Workflowdefinition den Typ Activity aufweisen. In diesem Beispiel wird die Divide-Aktivität von CodeActivity<int> abgeleitet. Sie wird jedoch als Activity deklariert, sodass eine nicht generische Überladung von Invoke verwendet wird, die ein Wörterbuch mit Argumenten anstelle eines einzelnen Rückgabewerts zurückgibt.

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

Verwenden von WorkflowApplication

WorkflowApplication stellt einen umfangreichen Satz von Funktionen für die Workflowinstanzverwaltung bereit. WorkflowApplication dient als thread-sicherer Proxy für das eigentlicheWorkflowInstance, das die Laufzeit kapselt und Methoden für das Erstellen und Laden von Workflow-Instanzen, das Anhalten und Fortsetzen, das Beenden und die Benachrichtigung über Lebenszyklusereignisse bereitstellt. Zum Ausführen eines Workflows mit WorkflowApplication erstellen Sie das WorkflowApplication-Objekt, abonnieren gewünschte Lebenszyklusereignisse, starten den Workflow und warten dann auf die Beendigung. In diesem Beispiel wird eine Workflowdefinition erstellt, die aus einer WriteLine-Aktivität besteht, und es wird eine WorkflowApplication unter Verwendung der angegebenen Workflowdefinition erstellt. Completed wird so behandelt, dass der Host bei Abschluss des Workflows benachrichtigt wird, der Workflow mit einem Aufruf von Run beginnt und der Host auf den Abschluss des Workflows wartet. Nach Abschluss des Workflows wird das AutoResetEvent-Objekt festgelegt, und die Hostanwendung kann die Ausführung fortsetzen, wie im folgenden Beispiel gezeigt.

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

WorkflowApplication-Lebenszyklusereignisse

Zusätzlich zum Fall Completed können Hostautoren benachrichtigt werden, wenn ein Workflow entladen wird (Unloaded), abgebrochen wurde (Aborted), in den Leerlauf wechselt (Idle und PersistableIdle) oder eine nicht behandelte Ausnahme auftritt (OnUnhandledException). Workflowanwendungsentwickler können diese Benachrichtigungen behandeln und entsprechend reagieren, wie im folgenden Beispiel gezeigt.

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

Festlegen der Eingabeargumente eines Workflows

Daten können beim Start eines Workflows mithilfe eines Wörterbuchs mit Parametern in den Workflow übergeben werden. Diese Vorgehensweise ähnelt dem Übergeben von Daten bei der Verwendung des WorkflowInvoker-Objekts. Jedes Element im Wörterbuch ist einem Eingabeargument des angegebenen Workflows zugeordnet. In diesem Beispiel wird ein Workflow aufgerufen, der aus einer WriteLine-Aktivität besteht, und das zugehörige Text-Argument wird mit dem Wörterbuch von Eingabeparametern angegeben.

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

Abrufen der Ausgabeargumente eines Workflows

Wenn ein Workflow abgeschlossen wird, können alle Ausgabeargumente in den Completed-Handler abgerufen werden, indem auf das WorkflowApplicationCompletedEventArgs.Outputs-Wörterbuch zugegriffen wird. Im folgenden Beispiel wird ein Workflow mit WorkflowApplication gehostet. Eine WorkflowApplication-Instanz wird mithilfe einer Workflow-Definition erstellt, die aus einer einzelnen DiceRoll-Aktivität besteht. Die DiceRoll-Aktivität verfügt über zwei Ausgabeargumente, die die Ergebnisse des Würfelvorgangs darstellen. Wenn der Workflow abgeschlossen ist, werden die Ausgaben im Completed-Handler abgerufen.

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

Hinweis

Das WorkflowApplication-Objekt und das WorkflowInvoker-Objekt akzeptieren ein Wörterbuch mit Eingabeargumenten und geben ein Wörterbuch mit out-Argumenten zurück. Diese Wörterbuchparameter, Eigenschaften und Rückgabewerte haben den Typ IDictionary<string, object>. Die tatsächliche Instanz der Wörterbuchklasse, die übergeben wird, kann jede Klasse sein, die IDictionary<string, object> implementiert. In diesen Beispielen wird Dictionary<string, object> verwendet. Weitere Informationen über Wörterbücher finden Sie unter IDictionary<TKey,TValue> und Dictionary<TKey,TValue>.

Übergeben von Daten in einen ausgeführten Workflow mithilfe von Lesezeichen

Lesezeichen stellen den Mechanismus dar, mit dem eine Aktivität passiv abwarten kann, bis sie wieder aufgenommen wird. Sie sind außerdem ein Mechanismus für das Übergeben von Daten in eine ausgeführte Workflowinstanz. Wenn eine Aktivität auf Daten wartet, kann sie ein Bookmark-Objekt erstellen und eine Rückrufmethode registrieren, die aufgerufen werden soll, wenn das Bookmark-Objekt wieder aufgenommen wird, wie im folgenden Beispiel gezeigt.

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

Bei der Ausführung erstellt die ReadLine-Aktivität ein Bookmark-Objekt, registriert einen Rückruf und wartet dann darauf, dass das Bookmark-Objekt wieder aufgenommen wird. Bei der Wiederaufnahme weist die ReadLine-Aktivität die Daten zu, die mit dem Bookmark-Objekt an das Result-Argument übergeben wurden. In diesem Beispiel wird ein Workflow erstellt, der mit der ReadLine-Aktivität den Namen des Benutzers erfasst und im Konsolenfenster anzeigt.

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

Wenn die ReadLine-Aktivität ausgeführt wird, erstellt sie ein Bookmark-Objekt mit dem Namen UserName und wartet dann darauf, dass das Lesezeichen wieder aufgenommen wird. Der Host sammelt die gewünschten Daten und nimmt das Bookmark-Objekt dann wieder auf. Der Workflow wird fortgesetzt, zeigt den Namen an und wird dann abgeschlossen.

Die Hostanwendung kann den Workflow überprüfen, um zu bestimmen, ob aktive Lesezeichen vorhanden sind. Hierfür werden die GetBookmarks-Methode einer WorkflowApplication-Instanz aufgerufen oder die WorkflowApplicationIdleEventArgs im Idle-Handler überprüft.

Das folgende Codebeispiel ähnelt dem vorherigen Beispiel, bis auf die Tatsache, dass die aktiven Lesezeichen aufgelistet werden, bevor das Lesezeichen wieder aufgenommen wird. Der Workflow wird gestartet, und sobald das Bookmark erstellt wurde und der Workflow in den Leerlauf übergeht, wird GetBookmarks aufgerufen. Wenn der Workflow abgeschlossen ist, wird die folgende Ausgabe in der Konsole angezeigt.

Wie heißen Sie?
BookmarkName: UserName - OwnerDisplayName: ReadLineSteveHallo, 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());

Im folgenden Codebeispiel werden die in den WorkflowApplicationIdleEventArgs-Handler einer Idle-Instanz übergebenen WorkflowApplication überprüft. In diesem Beispiel geht der Workflow in den Leerlauf über und verfügt über ein Bookmark mit dem Namen EnterGuess, das sich im Besitz einer Aktivität mit dem Namen ReadInt befindet. Dieses Codebeispiel beruht auf Vorgehensweise: Ausführen eines Workflows, der Teil des Tutorials "Erste Schritte" ist. Wenn der Idle-Handler in diesem Schritt geändert wird, um den Code aus diesem Beispiel zu enthalten, wird die folgende Ausgabe angezeigt.

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

Zusammenfassung

WorkflowInvoker bietet eine einfache Möglichkeit für das Aufrufen von Workflows. Es stellt auch Methoden zum Übergeben von Daten zu Beginn eines Workflows und für das Extrahieren von Daten aus einem abgeschlossenen Workflow bereit, bietet jedoch keine komplexeren Szenarien, in denen WorkflowApplication verwendet werden kann.