devxlogo

Execute a Long-Running Workflow Using Persistence in Windows WF

Execute a Long-Running Workflow Using Persistence in Windows WF

n a previous article on Windows Workflow, I discussed the basics of workflow and how workflows can be hosted in your Windows and console applications using the WorkflowRuntime class. In this article, I will discuss how you can manage long-running workflow applications by persisting them in databases and then restoring them back into memory.

By default, a workflow hosting application executes the workflow entirely in memory?from beginning to end. However, there are times when you need to execute long-running workflows?one which may not conclude until a couple of hours, or even days, later. In such cases, it is necessary to persist the workflow instance to an external medium so that the workflow can be continued later. This is necessary because workflow can be a real resource hog, occupying more than its share of CPU and memory; by persisting them to external medium you can free up valuable resources.

In Windows Workflow, you can use the SqlWorkflowPersistenceService service to persist a workflow instance to a SQL database server. To use the SqlWorkflowPersistenceService service, you first need to manually prepare the database.

Preparing the Database

Figure 1. Connecting: Launch the SQL Server Management Studio and provide the name of your database server.

The SqlWorkflowPersistenceService service works with either SQL Server 2000, SQL Server 2005, or SQL Server 2005 Express Edition. For this article, I used the latter.

The first step is to create the tables used for persistence by running a couple of scripts. The two scripts are: SqlPersistenceService_Schema.sql and SqlPersistenceService_Logic.sql.

For SQL Server 2005 Express, you can download the Microsoft SQL Server Management Studio Express to help you do the work. (For other versions of SQL server, you can use the Query Analyzer.) Once installed, launch Microsoft SQL Server Management Studio Express and specify the name of your database server (see Figure 1). If your SQL Server 2005 Express is installed locally, it should be “.SQLEXPRESS“.

Once connected, create a new database by right-clicking on the Databases item (see Figure 2) and select New Database?. Name the database SqlPersistenceService and click OK. You should be able to see the newly created database under the Databases item. Right-click on the new database name and select New Query (see Figure 3).


Figure 2. Creating: You need to create a new database to hold the workflow persistence.
?
Figure 3. New Query: Create a new query under the database instance you just created.

Now you need to load the script provided by Microsoft to create the tables necessary for persistence. To do this select File?>Open?>File and open the SqlPersistenceService_Schema.sql file from: C:WINDOWSWinFXv3.0Windows Workflow FoundationSQLEN. The Connect To Server dialog will appear again.

Click on the Options Figure 4). Click Connect.


Figure 4. Associate the query with a specific database.
?
Figure 5. New Tables: You want to check that the scripts have run correctly and that two new tables have been added.

You can now execute the first script by clicking on the Execute button located on the menu bar. Repeat the entire sequence above to load and execute the second script, SqlPersistenceService_Logic.sql.

To verify that the setup is performed correctly, check that the SqlPersistenceService table now has two additional tables (see Figure 5). You may need to refresh the database to see the newly created tables.

Creating the Sample Application
With the database prepared, let?s now build a very simple workflow application to see how a workflow instance can be persisted.

Using Visual Studio 2005, create a new Sequential Workflow Console Application and name it C:PersistenceWorkflow.

In the Design View of WorkFlow1.vb, drag-and-drop the following Workflow activities (see also Figure 6):

  • Code
  • Delay

Set the properties of the activities as shown in Table 1.

Table 1. Workflow Activities and their Corresponding Properties.

Activity Property Value
codeActivity1 ExecuteCode Code1
delayActivity1 TimeoutDuration 00:00:05
codeActivity2 ExecuteCode Code2

In the Code View of Workflow1.vb, code the following:

    Private Sub Code1( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs)        Console.WriteLine("In CodeActivity1: " & Now.ToString)    End Sub    Private Sub Code2( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs)        Console.WriteLine("In CodeActivity2: " & Now.ToString)    End Sub

In Module1.vb, there is already some code written for you to host the workflow. Add to it the following code shown in bold typeface:

        Shared Sub Main()            Using workflowRuntime As New WorkflowRuntime()                AddHandler workflowRuntime.WorkflowCompleted, _                   AddressOf OnWorkflowCompleted                AddHandler workflowRuntime.WorkflowTerminated, _                   AddressOf OnWorkflowTerminated                AddHandler workflowRuntime.WorkflowIdled, _                   AddressOf OnWorkflowIdled                AddHandler workflowRuntime.WorkflowPersisted, _                   AddressOf OnWorkflowPersisted                AddHandler workflowRuntime.WorkflowUnloaded, _                   AddressOf OnWorkflowUnloaded                AddHandler workflowRuntime.WorkflowLoaded, _                   AddressOf OnWorkflowLoaded                workflowRuntime.AddService( _                   New SqlWorkflowPersistenceService( _                   "Initial Catalog=SqlPersistenceService;" & _                   "Data Source=.SQLEXPRESS;Integrated " & _                   "Security=SSPI;"))                Dim workflowInstance As WorkflowInstance                workflowInstance = _                   workflowRuntime.CreateWorkflow(GetType(Workflow1))                workflowInstance.Start()                WaitHandle.WaitOne()                Console.ReadLine()            End Using        End Sub

Essentially, you are adding the other event handlers for the workflow so that you’ll be able to tell when the workflow is idled, persisted, loaded, and unloaded. In addition, this code adds a new SqlWorkflowPersistenceService service to the workflow. This will allow an instance of your workflow to be persisted to a SQL database when persistence should occur. The connection string to the database is passed as a parameter to this service.


Figure 6. Workflow: Design the workflow with two activities: code and delay.
?
Figure 7. You can learn a lot about what series of events occurred when the application runs by observing the messages printed when the workflow executes.

I also added a Console.ReadLine() statement so that the console window will not be closed immediately after the workflow finishes execution. This will allow you to observe the values printed on the screen.

In the OnWorkflowCompleted and OnWorkflowTerminated event handlers, add the two WriteLine() statements as shown in bold typeface below:

        Shared Sub OnWorkflowCompleted( _           ByVal sender As Object, _           ByVal e As WorkflowCompletedEventArgs)            Console.WriteLine("Workflow completed.")            WaitHandle.Set()        End Sub        Shared Sub OnWorkflowTerminated( _           ByVal sender As Object, _           ByVal e As WorkflowTerminatedEventArgs)            Console.WriteLine("Workflow terminated.")            Console.WriteLine(e.Exception.Message)            WaitHandle.Set()        End Sub

Add the following event handlers:

        Shared Sub OnWorkflowIdled( _           ByVal sender As Object, _           ByVal e As WorkflowEventArgs)            Console.WriteLine("Workflow idle.")            ThreadPool.QueueUserWorkItem( _               New WaitCallback(AddressOf UnloadInstance), _               e.WorkflowInstance)        End Sub

The OnWorkflowIdled event is invoked when a workflow goes into an idle state, such as when executing the Delay activity. For thread safety, you need to queue the events raised back from a workflow on the ThreadPool class.

The next event handler is the OnWorkFlowPersisted event:

        Shared Sub OnWorkflowPersisted( _           ByVal sender As Object, _           ByVal e As WorkflowEventArgs)            Console.WriteLine("Workflow persisted.")        End Sub

This event handler is invoked when the workflow is persisted. It implicitly calls the SaveWorkflowInstanceState method of the SqlPersistenceService class to save the workflow instance.

Once the workflow instance is persisted, the OnWorkflowUnloaded event is fired:

        Shared Sub OnWorkflowUnloaded( _           ByVal sender As Object, _           ByVal e As WorkflowEventArgs)            Console.WriteLine("Workflow unloaded.")        End Sub

This event is invoked when the workflow is unloaded from memory. This is performed by the UnloadInstance() method, which was referenced earlier in the OnWorkflowIdled event. The main purpose of the UnloadInstance() method is to perform the unloading of workflow instance.

        Shared Sub UnloadInstance( _           ByVal workflowInstance As Object)            CType(workflowInstance, WorkflowInstance).TryUnload()        End Sub

When a workflow instance is to be resumed, it must be loaded back into memory. This is performed by the OnWorkflowLoaded event. It implicitly calls the LoadWorkflowInstanceState method of the SqlPersistenceService class to load the saved workflow instance into memory:

        Shared Sub OnWorkflowLoaded( _           ByVal sender As Object, _           ByVal e As WorkflowEventArgs)            Console.WriteLine("Workflow loaded.")        End Sub

Press F5 to test the application. Figure 7 shows the output observed. Notice that there is a delay of five seconds between the first and second Code activities. Also note the series of events that were fired when the workflow instance was persisted to the SQL database and then loaded back into memory.

Manually Unloading and Loading the Workflow Instance
The previous example showed how a workflow runs and then is automatically persisted when it encounters the Delay activity. Besides this, you can also manually persist a workflow instance. To see how this is done, let’s use a Windows application as the host in this next example.

Using Visual Studio 2005, create a new Sequential Workflow Library project and name it C:WorkflowLibrary1.

Create the workflow in Workflow1.vb as shown in Figure 8. This is similar to the workflow created earlier, without the delayActivity activity.

In the code-behind of Workflow1.vb, code the following:

    Private Sub Code1( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs)        Console.WriteLine("In CodeActivity1: " & Now.ToString)        System.Threading.Thread.Sleep(5000)    End Sub    Private Sub Code2( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs)        Console.WriteLine("In CodeActivity2: " & Now.ToString)    End Sub

Note that I have inserted a Thread.Sleep() statement to simulate the activity taking a long time to process. In this case, the delay is five seconds.

Build the WorkflowLibrary1 project by right-clicking on its project name in Solution Explorer and then select Build.

Add a new Windows application project to the current solution. Name it C:PersistenceWorkflowWindow. Add the System.Workflow.Runtime assembly to the project (right-click on project name and select Add Reference?). Also add the WorkflowLibrary1.dll that you just built to the project.

Add three Button controls to the default Form1 (see Figure 9). The three buttons are to start a workflow, unload a workflow from memory, and to load a workflow into memory.


Figure 8. This second workflow is the same as the earlier one without the Delay activity.
?
Figure 9. Populate the default Form1 with three Button controls.

Switch to the code-behind of Form1 and import the following namespace:

Imports System.Workflow.RuntimeImports System.Workflow.Runtime.HostingImports System.Threading

Declare the following member variables:

Public Class Form1    Private WaitHandle As AutoResetEvent    Private workflowRuntime As New WorkflowRuntime()    Private workflowInstance As WorkflowInstance

In the Load event of the form, add the various event handlers to the WorkflowRuntime object and add the SqlPersistenceService service to it:

    Private Sub Form1_Load( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs) Handles MyBase.Load        WaitHandle = New AutoResetEvent(False)        '---monitor workflow events---        AddHandler workflowRuntime.WorkflowCompleted, _           AddressOf OnWorkflowCompleted        AddHandler workflowRuntime.WorkflowTerminated, _           AddressOf OnWorkflowTerminated        AddHandler workflowRuntime.WorkflowUnloaded, _           AddressOf OnWorkflowUnloaded        AddHandler workflowRuntime.WorkflowPersisted, _           AddressOf OnWorkflowPersisted        AddHandler workflowRuntime.WorkflowIdled, _           AddressOf OnWorkflowIdled        AddHandler workflowRuntime.WorkflowLoaded, _           AddressOf OnWorkflowLoaded        workflowRuntime.AddService(( _           New SqlWorkflowPersistenceService( _           "Initial Catalog=SqlPersistenceService;" & _           "Data Source=.SQLEXPRESS;Integrated" & _           " Security=SSPI;")))    End Sub

Define the event handlers as you did previously:

    Private Sub OnWorkflowCompleted( _       ByVal sender As Object, _       ByVal e As WorkflowCompletedEventArgs)        Console.WriteLine("Workflow completed.")        WaitHandle.Set()    End Sub    Private Sub OnWorkflowTerminated( _     ByVal sender As Object, _     ByVal e As WorkflowTerminatedEventArgs)        Console.WriteLine("Workflow terminated.")        Console.WriteLine(e.Exception.Message)        WaitHandle.Set()    End Sub    Private Sub OnWorkflowUnloaded( _      ByVal sender As Object, _      ByVal e As WorkflowEventArgs)        Console.WriteLine("Workflow unloaded.")    End Sub    Private Sub OnWorkflowPersisted( _       ByVal sender As Object, _       ByVal e As WorkflowEventArgs)        Console.WriteLine("Workflow persisted.")    End Sub    Private Sub OnWorkflowIdled( _       ByVal sender As Object, _       ByVal e As WorkflowEventArgs)        Console.WriteLine("Workflow idle.")        ThreadPool.QueueUserWorkItem( _           New WaitCallback(AddressOf UnloadInstance), _           e.WorkflowInstance)    End Sub    Private Sub UnloadInstance( _       ByVal workflowInstance As Object)        CType(workflowInstance, WorkflowInstance).TryUnload()    End Sub    Private Sub OnWorkflowLoaded( _       ByVal sender As Object, _       ByVal e As WorkflowEventArgs)        Console.WriteLine("Workflow loaded.")    End Sub

In the Click event of the Start button, create a new thread that invokes the StartWorkflow() subroutine, which will be defined next:

    Private Sub btnStart_Click( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs) _       Handles btnStart.Click        Dim t1 As New Thread(AddressOf StartWorkflow)        t1.Start()    End Sub

The StartWorkflow() subroutine creates a new workflow instance and starts it. You must start it on a separate thread because once the workflow instance is started, the user interface of the Windows application will be frozen and it will then be impossible to click on the Unload button to unload the workflow.

    Private Sub StartWorkflow()        workflowInstance = _           workflowRuntime.CreateWorkflow( _           GetType(WorkflowLibrary1.Workflow1))        workflowInstance.Start()        '---wait for the event to be signaled---        WaitHandle.WaitOne()    End Sub

To manually unload an executing workflow instance, use the Unload() method, as shown here in the Click event of the Unload button:

    Private Sub btnUnload_Click( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs) _       Handles btnUnload.Click        Try            workflowInstance.Unload()        Catch ex As Exception            MsgBox(ex.ToString)        End Try    End Sub

Once a workflow instance is unloaded, it will be persisted to the SQL database. To load the workflow instance, code the Click event of the Load button and use the Load() method:

    Private Sub btnLoad_Click( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs) _       Handles btnLoad.Click        Try            workflowInstance.Load()        Catch ex As Exception            MsgBox(ex.ToString)        End Try    End Sub

You are now ready to test the application. Set the Windows application as the startup project and press F5. To view the messages printed by the Console.WriteLine() statements, select View?>Output in Visual Studio 2005.

Figure 10 shows what happened when I allowed the workflow to run from beginning to end. In this example, I clicked on the Start button and let the workflow execute till its conclusion.


Figure 10. Here I’ve run a workflow from beginning to end with no interruptions.
?
Figure 11. Here I interrupted the workflow by manually requesting to unload and load a workflow instance.

Figure 11 shows what happened when the workflow is started and then unloaded and finally loaded again. I clicked the Start button to start the workflow and, later, unload (by clicking on the Unload button) while it was still executing. Finally, I clicked the Load button to load the workflow instance from the SQL database.

In this article, you have seen how long-running workflows can be persisted to external media such as a SQL Server database. You have also seen how to manually unload a workflow instance from memory. Persisting workflows is important as it conserves valuable memory and allows you to execute long-running workflows without worrying about resource constraints.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist