views:

500

answers:

3

I'm using Windows WF right now for a simple way to state machines. In fact, I'm not even using a state machine, I'm using a sequential workflow. Eventually, I will ditch WF in favor of something else, but since I already have the code working, I need to get the Abort, Suspend, and Resume methods working.

My application spawns a thread, which then spawns another thread that owns the WorkflowInstance. My GUI has Abort, Pause, and Resume buttons in it, and these eventually call the WorkflowInstance's Abort, Suspend, and Resume methods, respectively.

The problem is that when I do this, I get a very large and scary MessageBox that says:

The workflow hosting environment does not have a persistence service as required by an operation on the workflow instance

along with a nice stack trace and all. Now, I looked up these methods in Pro WF by Bruce Bukovics and one of his examples calls these methods, and no mention of a "persistence service" was anywhere. However, his example calls were within the scope of the WorkflowRuntime, i.e he calls them like this:

using(WorkflowRuntimeManager manager = new WorkflowRuntimeManager(new WorkflowRuntime("WorkflowRuntime")))
{
  manager.WorkflowRuntime.StartRuntime();
  WorkflowInstanceWrapper instance = manager.StartWorkflow(typeof(SharedWorkflows.Workflow1), null);
  instance.Suspend("Manually suspended");
  instance.Resume();
  waitHandle.WaitOne();
}

In my app, I implemented the WorkflowRuntime as a singleton because I found that there was a huge memory leak when I created the WorkflowRuntime like this. So my code looks like this:

WorkflowInstance instance = WorkflowRuntimeSingleton.Instance.workflow_runtime.CreateWorkflow(typeof(SharedWorkflows.Workflow1), null);
instance.Start();
instance.Suspend("Manually suspended");
instance.Resume();
waitHandle.WaitOne();

Now, if I call Suspend and Resume as shown above, it works fine. But if I issue the call via my GUI, it complains about the persistence service.

Given this information, and that I do not want to set up a database just to get these three functions, I'd like to know what I need to do to make this work. My best guess at this point is that WF doesn't like being controlled from a separate thread. If that's the case, is there a good way to make the call seem as though it is issued from the same thread?

Here are some possible solutions that I've come up with, but I'm sure someone here has a way fancier and elegant way to do it.

  1. WF polls for abort / pause / resume via an interface to GUI (seems really lame)
  2. Replace the WaitOne() with WaitAny(), and have the GUI call into the object that owns the workflow set an AutoResetEvent. WaitAny() allows execution to continue, and then my code can check to see which button the user pressed. This would need to be wrapped in a loop so that we can wait again until the user clicks Abort, or until the WF is complete.
  3. use a boolean flag to basically do what #2 is doing.
  4. see if anyone on SO knows how to make the call magically come in on the right thread :)

Any insight or opinions would be really appreciated!

+3  A: 

Its not that big of a deal to create the persistence database. In fact, it will help your memory problems, because it persists the workflows that are suspended for longer than a given period of time (taking them out of memory). Here is a link to help you create the database and use it in your workflow: http://msdn.microsoft.com/en-us/library/ms735722(VS.85).aspx

In the link, it mentions changing your app.config. I didn't do this. Instead, I added the service in code. Like this:

//Add the persistence service
WorkflowPersistenceService persistenceService = new SqlWorkflowPersistenceService(
    DBConnections.PersistenceService,
    true,
    TimeSpan.MaxValue,
    new TimeSpan(0, 0, 15));
m_WorkflowRuntime.AddService(persistenceService);

EDIT: Another helpful link

Gabriel McAdams
thank you, I will give this a try very soon!
Dave
I made the app.config file as described, but then re-read your answer and tried your code-behind approach instead. How is DBConnections declared in your code?
Dave
@Gabriel: sorry, I was lazy and didn't dig through MSDN. I see that it's the database connection string. I'll have to figure out the right string for connecting to a database file.
Dave
how nice! VS2008 actually tells me the connection string for a data connection in the properties tab... thank goodness. :)
Dave
Gabriel McAdams
Thanks!!! I was just about to do this manually, but now I shouldn't have to. :)
Dave
A: 

I'm posting this as an answer so that I can get decent formatting. I have followed Gabriel's answer, and I am having the darnedest time trying to get the database configured. I have several questions regarding this.

All of the links mention creating the database using Microsoft SQL Server Query Analyzer, which I don't have. Instead, I went to the Server Explorer in VS2008, right-clicked Data Connections -> Create New SQL Server Database. I used Windows Authentication and selected my computer from the droplist of servers.

I want to be able to run this code on multiple systems, using the same database file. Why can't I specify localhost here?

Regarding the above question, if I instead create a new database using Data Connections -> Add Connection, I can create a local database file that I can include in my solution, and presumably move from PC to PC. This is probably the right way to go, but

What's the major difference between using "Microsoft SQL Server Compact 3.5" and "Microsoft SQL Server Database File"? Both allow me to create a file. I like the Compact selection better, because I don't need to use a password, but I don't know if this then requires some other special service to be installed on another computer that the Database File option doesn't need.

Moving on, I then have to execute an SQL query to generate the table for the workflow persistence store. According to the linked pages, this location is:

%WINDIR%\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\<language>\SqlPersistence_Schema

Since I don't have the Query Analyzer, I figured that executing a query from VS2008 should work well enough. If I copy and paste the SQL into the query window, I get this error dialog:

Query Definitions Differ

The following errors were encountered while parsing the contents of the SQL pane:
The Set SQL construct or statement is not supported.

followed by:

SQL Execution Error.

Executed SQL statement: -- Copyright (c) Microsoft Corporation. All rights reserved.

SET NOCOUNT ON

--
-- ROLE state_persistence_users
--
declare @localized_string_AddRole_Failed nvarchar(256)
set @localized_string_AddRole_Failed = N'Failed adding the "state_per...
Error Source: .Net SqlClient Data Provider
Error Message: Incorrect syntax near the keyword 'if'.
Incorrect syntax near 'GO'.
Incorrect syntax near the keyword 'CREATE'.
Incorrect syntax near the keyword 'IF'.
Incorrect syntax near 'GO'.
Incorrect syntax near the keyword 'CREATE'.
Incorrect syntax near the keyword 'CREATE'.
Incorrect syntax near the keyword 'DBCC'.
Incorrect syntax near ')'.

Does anyone know other ways I can try to create the persistence store?

Dave
+1  A: 

SQL Server Compact won't work because CE does not support stored procedures, which are part of the default persistence DB creation script and are invoked by the SQL Persistence Service. CE is meant to embed the engine within an application. DB file would be used with something like SQL Server Express, where the engine is run in a separate process but you can point to a DBF file rather than connecting to a DB already attached to the engine.

By your question/response, it sounds like you don't expect a workflow created with one instance of the app to be recalled with another instance (shared workflows). One additional possibility is not to use the SQL version of the persistence service. There is a sample (may not be complete, not sure) of a workflow persistence service that is based upon direct serialization of the workflow to a file at http://msdn.microsoft.com/en-us/library/ms741725.aspx. I don't know if it supports all your needs, but since it includes source, you may be able to tweak it.

Rich
Thanks, Rich, I guess I got lucky and picked the right one. I'm using SQL Express with the DBF file in my project folder.You're right, I'm not using shared workflows, so I will look into serializing to a file. Ultimately, that's what I'd really like to do. Actually, I don't want to persist anywhere at all -- I wouldn't mind having something designed around AutoResetEvents and WaitHandles, but WF is what I started with for this app, and I'm going to try to do it the WF way temporarily. Thanks a bunch for the link.
Dave
You say you don't want to persist at all. Unless your app will never want to pause a workflow, close the app, reopen it, and resume the workflow, you're going to need a persistence service.
Rich