使用 WorkflowInvoker 與 WorkflowApplication

Windows Workflow Foundation (WF) 提供數種裝載工作流程的方法。 WorkflowInvoker 提供一種簡單方法來叫用工作流程,如同方法呼叫一般,但只能用於不使用持續性的工作流程。 WorkflowApplication 提供更豐富的模型,可執行包含生命週期事件通知、執行控制、書籤繼續以及持續性的工作流程。 WorkflowServiceHost 支援傳訊活動,主要搭配工作流程服務使用。 本主題會向您介紹如何使用 WorkflowInvokerWorkflowApplication 進行工作流程裝載。 如需使用 WorkflowServiceHost 的裝載工作流程詳細資訊,請參閱工作流程服務裝載工作流程服務概觀

使用 WorkflowInvoker

WorkflowInvoker如同方法叫用般,提供執行工作流程的模型。 若要使用 WorkflowInvoker 叫用工作流程,請呼叫 Invoke 方法,並傳入要叫用之工作流程的工作流程定義。 在此範例中,會使用 WriteLine 叫用 WorkflowInvoker 活動。

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

WorkflowInvoker.Invoke(wf);

使用 WorkflowInvoker 叫用工作流程時,工作流程會在呼叫執行緒上執行,且 Invoke 方法會封鎖至工作流程完成為止,並包含任何閒置時間。 若要設定工作流程必須完成的逾時間隔,請使用接受 Invoke 的其中一個 TimeSpan 多載。 在此範例中,會以兩個不同的逾時間隔叫用工作流程兩次。 第一個工作流程會完成,但是第二個則不會。

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

注意

只有在超過逾時間隔及工作流程在執行期間變成閒置狀態時,才會擲回 TimeoutException。 需要比指定的逾時間隔還長的時間才能完成的工作流程,會在工作流程沒有變成閒置狀態時成功完成。

WorkflowInvoker 也提供叫用方法的非同步版本。 如需詳細資訊,請參閱 InvokeAsyncBeginInvoke

設定工作流程的輸入引數

若要將資料傳入工作流程,可以使用以引數名稱做為索引鍵,且對應工作流程輸入引數之輸入參數的字典。 在此範例中,會叫用 WriteLine 並使用輸入參數的字典指定其 Text 引數的值。

Activity wf = new WriteLine();

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

WorkflowInvoker.Invoke(wf, inputs);

擷取工作流程的輸出引數

工作流程的輸出引數可以使用從 Invoke 呼叫傳回的輸出字典來取得。 下列範例會叫用由具有兩個輸入引數和兩個輸出引數之單一 Divide 活動組成的工作流程。 叫用此工作流程時,系統會傳遞包含每個輸入引數之值 (以引數名稱做為索引鍵) 的 arguments 字典。 當傳回 Invoke 的呼叫時,每個輸出引數都會傳入 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"]);

如果此工作流程衍生自 ActivityWithResult (例如 CodeActivity<TResult>Activity<TResult>),而且除了定義完善的 Result 輸出引數以外,還有其他輸出引數,您就必須使用 Invoke 的非泛型多載,才能擷取其他引數。 若要這樣做,傳遞給 Invoke 的工作流程定義必須屬於 Activity 型別。 在此範例中,Divide 活動會衍生自 CodeActivity<int> 但宣告為 Activity,以便使用 Invoke 的非泛型多載 (傳回引數的字典而非單一傳回值)。

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

使用 WorkflowApplication

WorkflowApplication 提供一組豐富的工作流程執行個體管理功能。 WorkflowApplication 可做為實際 WorkflowInstance 的執行緒安全 Proxy (可封裝執行階段),並提供建立及載入工作流程執行個體、暫停與繼續、終止,以及生命週期事件通知的方法。 若要使用 WorkflowApplication 執行工作流程,您要建立WorkflowApplication、訂閱任何想要的開發週期事件、啟動工作流程,然後等候它完成。 在此範例中,會建立由 WriteLine 活動組成的工作流程定義,並使用特定的工作流程定義建立 WorkflowApplicationCompleted 會進行處理,如此一來,當工作流程完成時即會通知主機應用程式,會呼叫 Run 來開始工作流程,然後主機應用程式會等候工作流程完成。 當工作流程完成時,會設定 AutoResetEvent,且主機應用程式可繼續執行,如下列範例所示。

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

工作流程應用程式開發週期事件

除了 Completed 以外,當卸載工作流程 (Unloaded)、中止 (Aborted)、變成閒置 (IdlePersistableIdle) 或發生未處理的例外狀況 (OnUnhandledException) 時,也可通知主機作者。 工作流程應用程式開發人員可處理這些通知,並採取適當行動,如下列範例所示。

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

設定工作流程的輸入引數

與使用 WorkflowInvoker 傳入資料的方式相似,當開始工作流程時,可使用參數字典將資料傳入工作流程內。 字典中的每一項都對應至指定工作流程的輸入引數。 在此範例中,會叫用由 WriteLine 活動構成的工作流程,且會使用輸入參數的字典指定其 Text 引數。

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

擷取工作流程的輸出引數

當工作流程完成時,可存取 Completed 字典來擷取 WorkflowApplicationCompletedEventArgs.Outputs 處理常式中的任何輸出引數。 下列範例會使用 WorkflowApplication 來裝載工作流程。 系統會使用由單一 DiceRoll 活動組成的工作流程定義來建構 WorkflowApplication 執行個體。 DiceRoll 活動具有兩個輸出引數,這些引數代表擲骰作業的結果。 工作流程完成時,便會在 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();

注意

WorkflowApplicationWorkflowInvoker 會採用輸入引數的字典,並傳回 out 引數的字典。 這些字典參數、屬性與傳回值都屬於型別 IDictionary<string, object>。 傳入的字典類別實際執行個體,可以是任何實作 IDictionary<string, object> 的類別。 在這些範例中,會使用 Dictionary<string, object>。 如需字典的詳細資訊,請參閱IDictionary<TKey,TValue>Dictionary<TKey,TValue>

使用書籤將資料傳入執行中的工作流程

書籤是一種機制,可讓活動被動等候繼續,也可將資料傳遞至執行中的工作流程執行個體。 如果活動正在等候資料,就可建立 Bookmark 並註冊當恢復 Bookmark 時要呼叫的回呼方法,如下列範例所示。

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

執行時,ReadLine 活動就會建立 Bookmark、註冊回呼,然後等候繼續 Bookmark。 當它繼續時,ReadLine 活動會將以 Bookmark 傳遞的資料指派至其 Result 引數。 在此範例中,使用 ReadLine 活動建立的工作流程可蒐集使用者的名稱,並於主控台視窗中顯示。

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

當執行 ReadLine 活動時,它會建立稱為 BookmarkUserName,然後等候書籤繼續。 主機會收集想要的資料,然後繼續 Bookmark。 工作流程會繼續、顯示名稱,然後完成。

主應用程式可以檢查工作流程,以判斷是否有任何作用中的書籤。 若要進行這項處理,它可以呼叫 GetBookmarks 執行個體的 WorkflowApplication 方法,或是在 WorkflowApplicationIdleEventArgs 處理常式中檢查 Idle

下列程式碼範例與上一個範例很相似,例外之處是先列舉作用中的書籤,然後再繼續書籤。 系統會啟動工作流程,而且一旦建立 Bookmark 並且工作流程處於閒置狀態之後,就會呼叫 GetBookmarks。 當工作流程完成時,主控台就會顯示下列輸出。

What is your name?
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());

下列程式碼範例會檢查傳遞給 WorkflowApplicationIdleEventArgs 執行個體之 Idle 處理常式的 WorkflowApplication。 在此範例中,處於閒置狀態的工作流程具有一個名為 Bookmark 且由名為 EnterGuess 之活動所擁有的 ReadInt。 此程式碼範例是以如何:執行工作流程為基礎,這是使用者入門教學課程的一部分。 如果該步驟中的 Idle 處理常式修改為包含此範例中的程式碼,就會顯示下列輸出。

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

摘要

WorkflowInvoker 提供叫用工作流程的輕鬆方式,雖然它提供在工作流程之初傳入資料,以及從已完成之工作流程擷取資料的方法,但它並不提供可使用 WorkflowApplication 的更複雜案例。