Cannot load instance of StateMachineStateTracker. Maybe its reach Final State in WF - c#

First I'm totally new to WorkFlow Foundation 4.5. We use the WF engine for managing States in our Case entities. Instead of building our own state machine we decided to use WF. Mostly due to the fact that our customer have large process flows (not that complicated) that we wanted to draw in xaml. Easy for everybody to actually understand the process and talk about it.
The problem is that our transitions to the end state, Final state, results in that StateMachineStateTracker instance is null when we load it. This code below works perfectly for all transitions and we can load up tracker instance after resuming bookmark to see what the new current state is.
private void ConfigureWorkflowApplication(WorkflowApplication wfApp, SqlWorkflowInstanceStore store)
{
wfApp.InstanceStore = store;
var tracker = new StateMachineStateTracker(wfApp.WorkflowDefinition);
wfApp.Extensions.Add(tracker);
wfApp.Extensions.Add(new StateTrackerPersistenceProvider(tracker));
wfApp.Completed = delegate { Debug.WriteLine("Workflow completed."); };
wfApp.Aborted =
delegate(WorkflowApplicationAbortedEventArgs e)
{
Debug.WriteLine("Workflow Aborted. Exception: {0}\r\n{1}", e.Reason.GetType().FullName,
e.Reason.Message);
};
wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
Debug.WriteLine("Unhandled Exception: {0}\r\n{1}", e.UnhandledException.GetType().FullName,
e.UnhandledException.Message);
return UnhandledExceptionAction.Terminate;
};
wfApp.PersistableIdle = delegate { return PersistableIdleAction.Unload; };
}
Code above instantiate a WorkFlowApplication instance.
protected bool Execute(Activity process, Case #case, string transition)
{
WorkflowApplicationInstance instance = null;
using (var store = new DisposableStore())
{
instance = WorkflowApplication.GetInstance(#case.InstanceId, store.Store);
var wfApp = new WorkflowApplication(process, WorkflowIdentity);
ConfigureWorkflowApplication(wfApp, store.Store);
var trackerInstance = StateMachineStateTracker.LoadInstance(#case.InstanceId, wfApp.WorkflowDefinition,
_connectionString);
if (!trackerInstance.Transitions.Any(x => x.DisplayName.Equals(transition))) return false;
}
using (var store = new DisposableStore())
{
var wfApp = new WorkflowApplication(process, instance.DefinitionIdentity);
ConfigureWorkflowApplication(wfApp, store.Store);
wfApp.Load(#case.InstanceId);
var sync = new AutoResetEvent(false);
wfApp.ResumeBookmark(transition, null);
wfApp.Unloaded = x => sync.Set();
sync.WaitOne();
// Set case to new state
var trackerInstance = StateMachineStateTracker.LoadInstance(#case.InstanceId, wfApp.WorkflowDefinition,
_connectionString);
#case.ChangeToNewState(trackerInstance.CurrentState);
}
return true;
}
The code above have the intention to Make a transition from one State to next (string transition) and we also want to set the new state to our Case class.
This fails when we want to do this from our State before Final state. No exception. No Logging in output window. Nothing. Just that the row
var trackerInstance = StateMachineStateTracker.LoadInstance(#case.InstanceId, wfApp.WorkflowDefinition,
_connectionString);
Returns null. Is this due to the fact that you cannot Load StateMachineStateTracker with an instance that is on final state (not sure if it actually reaches final state).
Do anybody have any clue about the problem? I have a feeling that this is something basic that we've forgot.

Ok. I found the problem. It was as I though. Not a problem. Just me being rookie on WF.
When you do transitions to final state, WF remove all data about case since its finished. This means SurrogateInstance is removed from DB and state machine state tracker are of course not working since there is no case to load into tracker.
BUT however I did a delegate method on Completed event for WorkflowApplication that can handle closure and finalize our case.
wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
Debug.WriteLine("Workflow completed.");
Debug.WriteLine("State" + e.CompletionState);
if (e.CompletionState == ActivityInstanceState.Closed)
{
_caseIsCompleted = true;
}
};

Related

How To Delete A File Being Used By Another Process?

Before I begin, I should mention that this isn't really something I want to do, I'm simply curious about how it works.
I have this method called AddLayer(), which opens a local geodatabase file and creates a map with it:
public async void AddLayer()
{
try
{
// open a geodatabase on the local device
gdb = await Geodatabase.OpenAsync(#"..\..\..\test.geodatabase");
// get the first geodatabase feature table
var gdbFeatureTable = gdb.FeatureTables.FirstOrDefault();
//create a layer for the feature table
lyr = new FeatureLayer
{
ID = gdbFeatureTable.Name,
DisplayName = gdbFeatureTable.Name,
FeatureTable = gdbFeatureTable
};
// add the graphics to the map
MyMapView.Map.Layers.Add(lyr);
}
catch (Exception ex)
{
MessageBox.Show("Unable to create offline database: " + ex.Message);
}
return;
}
I also have another method called RemoveLayer(), which simply un-does everything AddLayer() did and then calls GC.Collect().
My question is, why, even after no longer using the resources in the file and calling the garbage collector, can I not delete the file (the geodatabase file) while the program is running?
Is this normal behavior for all programs in Windows? Is it to avoid somehow corrupting the program?
Thank you all for helping me understand this.
This code below ended up working for me:
public void OpenGeodatabase()
{
Geodatabase gdb = null;
// path to .geodatabase
var gdbPath = #"..\..\..\test.geodatabase";
// wrap OpenAsync call in Task
Task.Run(async () =>
{
// open a geodatabase on the local device
gdb = await Geodatabase.OpenAsync(gdbPath);
}).Wait();
// get the first geodatabase feature table
var gdbFeatureTable = gdb.FeatureTables.FirstOrDefault();
// create a layer for the feature table
var lyr = new FeatureLayer
{
ID = gdbFeatureTable.Name,
DisplayName = gdbFeatureTable.Name,
FeatureTable = gdbFeatureTable
};
// add the graphics to the map
MyMapView.Map.Layers.Add(lyr);
// remove the layer - to make it similar to case explanation
MyMapView.Map.Layers.Remove(lyr);
// make gdb reference null
gdb = null;
gdbFeatureTable = null;
lyr = null;
// call garbage collector
GC.Collect();
GC.WaitForPendingFinalizers();
// If the works, the lock has been removed
System.IO.File.Delete(#"..\..\..\test.geodatabase");
}
Basically I did everything inside of a non-async method, wrapped the call to OpenAsync within a Task, then set everything made from the geodatabase to null after use. Finally, I called the garbage collector and deleted the file.

Multiple timers with different intervals, writing to the same target

I'm writing a Windows Service that will execute different data import logic, from different data source to eventually write it to a single target, a MS CRM instance. Right now, the only thing I think will be problematic, is the writing to CRM part. The concurent reading of data from different (sometimes same) data source shouldn't really be an issue (I may be wrong on this...) So I came up with a way to make sure there are no concurent writes (create or updates) to CRM.
Here's the general design for the moment:
What happens when the service starts:
Timers = new List<System.Timers.Timer>();
CrmTransactionQueue.Lock = new object { }; //Static class. The object for locking purposes...
System.Threading.Thread.Sleep(20000); //for debugging purpose so I can attach to process before everything kicks in...
//retrieve all types that are extending BaseSyncStrategy..
var strategyTypes = Assembly.GetExecutingAssembly().GetTypes().Where(x => x.BaseType == typeof(BaseSyncStrategy));
foreach (Type strategyType in strategyTypes)
{
//create a instance of that type....
var strategy = (BaseSyncStrategy)Activator.CreateInstance(strategyType);
//create a timer for each of these, they will have different intervals...
System.Timers.Timer t = new System.Timers.Timer
{
Interval = strategy.Interval * 1000,
AutoReset = false,
Enabled = true
};
Timers.Add(t);
t.Elapsed += (sender, e) => TimerElapsed(sender, e, strategy);
t.Start();
}
What happens when the timers' interval are expired:
private void TimerElapsed(object sender, ElapsedEventArgs e, BaseSyncStrategy strategy)
{
//get timer back
var timer = (Timer)sender;
try
{
strategy.Execute();
}
catch (Exception ex)
{
Logger.WriteEntry(EventLogEntryType.Error, $"Error executing strategy {strategy.GetType().Name}: ", ex);
}
timer.Start();
}
And within all the Execute methods of objects extending BaseSyncStrategy, each time I want to update or create something in the target CRM instance, I do this:
XrmServiceContext XrmCtx = new XrmServiceContext();
//....
//code that fetches data from foreign sources and creates CRM entities...
//....
Action<XrmServiceContext> action = (XrmServiceContext ctx) =>
{
//write those created/updated objects
//ctx lets me query entities and write back to CRM...
};
CrmTransactionQueue.Execute(action, XrmCtx);
And the simple code to make sure (I think) no concurent writes to CRM happen:
public static class CrmTransactionQueue
{
public static object Lock { get; set; }
public static void Execute(Action<XrmServiceContext> transaction, XrmServiceContext Ctx)
{
lock (Lock)
{
transaction.Invoke(Ctx);
}
}
}
Is this sound design or there's a better way to do this ?

WorkFlow Foundation 4 WorkflowApplication Completed not triggered

I have the following code
AutoResetEvent instanceUnloaded = new AutoResetEvent(false);
WFStepsActivity workflow = WorkflowFactory.Current.BuildWorkflow(workflowinstance, requestinstance.RequestInstanceID);
WorkflowApplication wf = new WorkflowApplication(workflow);
wf.InstanceStore = WFInstanceStore.GetInstanceStore();
WFStepsPersistenceParticipant persist = new WFStepsPersistenceParticipant();
wf.Extensions.Add(persist);
wf.PersistableIdle = (arg) =>
{
return PersistableIdleAction.Unload;
};
wf.Unloaded = (arg) =>
{
log.Info("WFName_" + wfsetting.Name + "_Unloaded and return.");
instanceUnloaded.Set();
};
wf.OnUnhandledException = (arg) =>
{
return UnhandledExceptionAction.Abort;
};
wf.Completed = (arg) =>
{
if (arg.CompletionState == ActivityInstanceState.Closed)
{
persist.WorklflowCompleted = true;
}
};
wf.Run();
instanceUnloaded.WaitOne();
return persist.WorklflowCompleted;
where the WFStepsActivity is just the Acitivity structure that we build from settings.
The workflow currently ends when a bookmark is created.
context.CreateBookmark(context.WorkflowInstanceId.ToString(), new System.Activities.BookmarkCallback(OnResumeBookmark));
The issue is after the bookmark is created the wf.Completed is never triggered.
Would like to check if the Complete is triggered if Bookmark is created.
As I know Complete event will be trigger if u Cancel, Terminate
Isn't it supposed that once the workflow stop of bookmarked, this event will be triggered?
Bookmarks induce the workflow to become Idle, meaning that you must purposefully resume at the bookmark to continue and complete the workflow. First things first: Grok bookmarks.
I'm not sure you have supplied enough code to allow anyone to properly diagnose the precise issue (I can't see your bookmark creation, nor your supporting classes etc).
It is also possible that your workflow is throwing an exception, the details of which are being swallowed. If you want to catch and log any unhandled exceptions I would ammend your code thusly:
Exception error = null;
wf.OnUnhandledException = (arg) =>
{
error = arg.UnhandledException;
return UnhandledExceptionAction.Abort;
};
// and then after you've waited for completion...
if (error != null) {
throw error; // or handle it another way
}
Also useful: Configuring Tracking for a Workflow

How to separate parallel requests?

I'll try to explain the issue with a simplified console application example, however the real project is a ASP.NET MVC3 application.
Having the following tables:
imagine the following scenario:
user creates a report (a line in TestReport, where Text is the report string content, and Ready is a bool flag, saying, if the report is ready to be processed); by default Ready is set to false, i.e. not ready.
user wants the report to be processed, so he submits it; Ready is set to true here.
The system gives an opportunity to recall the report back, if it has not been processed yet. So, when the report is recalled, Ready is set to false back. On the contrary, when the report is processed, a line in TestReportRef, referencing report by its Id, is created.
Now imagine that at one and the same moment
user wants to recall the report;
the report is added to the process list;
As soon as this can happen simultaneously, errors may occur. That is the report will have Ready == false and it'll be referenced in TestReportRef.
Here is a simple console example of how this may happen:
var dc = new TestDataContext('my connection string');
dc.TestReport.InsertOnSubmit(new TestReport
{
Text = "My report content",
Ready = true //ready at once
});
dc.SubmitChanges();
Action recallReport = () =>
{
var _dc = new TestDataContext(cs);
var report = _dc.TestReport.FirstOrDefault(t => t.Ready);
if (report != null && !report.TestReportRef.Any())
{
Thread.Sleep(1000);
report.Ready = false;
_dc.SubmitChanges();
}
};
Action acceptReport = () =>
{
var _dc = new TestDataContext(cs);
var report = _dc.TestReport.FirstOrDefault(t => t.Ready);
if (report != null && !report.TestReportRef.Any())
{
Thread.Sleep(1000);
_dc.TestReportRef.InsertOnSubmit(new TestReportRef
{
FK_ReportId = report.Id
});
_dc.SubmitChanges();
}
};
var task1 = new Task(recallReport);
var task2 = new Task(acceptReport);
task1.Start();
task2.Start();
task1.Wait();
task2.Wait();
foreach (var t in dc.TestReport)
{
Console.WriteLine(string.Format("{0}\t{1}\t{2}", t.Id, t.Text, t.Ready));
}
foreach (var t in dc.TestReportRef)
{
Console.WriteLine("ref id:\t" + t.FK_ReportId);
}
Thread.Sleep(1000); is added to be ensure, that tasks will check one and the same situation.
The given example may sound awkward, however, I hope, it should explain the issue I'm dealing with.
How can I avoid this? Making the repository singleton doesn't seem to be a good idea. Shall I use some shared mutex (one for all web requests) to separate write-operations only?
Or is there a pattern I should use in this kind of scenario?
This is only a simplified example of one of the scenarios I have. However, there are several scenarios in which it may run into a similar discrepancy. The best thing would be to make this kind of intersection impossible, I guess.
Why don't add a version column on the Report table? Task starts by tracking current version,when task end, if the version is the same that the tracked one, operation is ok, otherwise fail. If operation appear ok, update the version to version +1. This is a sort of optimistic lock; that implicitly suppose that conflicts may occur, but they are not so frequent.
UPDATE
If you are using linqto sql maybe you can have a check at the parameter UpdateCheck [Column(UpdateCheck=UpdateCheck.Always)]
This can be useful to handle concurrency in your case.

InstanceStore WaitForEvents not firing when using WorkflowApplication with a WorkflowIdentity

I posted this question on the microsoft forum but wanted to post it here for additional exposure. I have searched everywhere for an example of what I'm trying to do with no luck.
http://social.msdn.microsoft.com/Forums/en-US/wfprerelease/thread/8a424c0b-44f8-4670-8d64-9c7142117b55
Basically, I would like to use the WorkflowApplication along with the WorkflowIdentity and persistence store to accomplish durable delays. I use WorkflowApplication because we have hundreds of workflows that need to be run dynamically so the WorkflowServiceHost is not really feasible in our scenario.
I used the Microsoft provided example, AbsoluteDelay, as a starting point and it works perfectly. The problem comes when I try to pass in a WorkflowIdentity to the constructor of the WorkflowApplication.
Then, the WaitForEvents event on the InstanceStore does not fire because it is expecting the default Identity (id 1), not my new Identity (id 2). The line that does store.Execute creates a record in the IdentityOwnerTable with SurrogateIdentityId set to 1 instead of the id of my identity.
How can I tell the InstanceStore to use my new Identity and not the default one? It seems as if it is something that can be passed through in the CreateWorkflowOwnerCommand metadata.
// Create the InstanceStore
SqlWorkflowInstanceStore store = new SqlWorkflowInstanceStore(_workflowDB);
// A well known property that is needed by WorkflowApplication and the InstanceStore
private static readonly XName _workflowHostTypePropertyName = XNamespace.Get("urn:schemas-microsoft-com:System.Activities/4.0/properties").GetName("WorkflowHostType");
// Create the XName for the workflow using the workflow's unique name
XName wfHostTypeName = XName.Get(workflowName);
// Configure a Default Owner for the instance store so instances can be re-loaded from WorkflowApplication
private static InstanceHandle CreateInstanceStoreOwner(InstanceStore store, XName wfHostTypeName)
{
InstanceHandle ownerHandle = store.CreateInstanceHandle();
CreateWorkflowOwnerWithIdentityCommand ownerCommand = new CreateWorkflowOwnerWithIdentityCommand()
{ //changed from CreateWorkflowOwnerCommand (did not fix problem)
InstanceOwnerMetadata =
{
{ _workflowHostTypePropertyName, new InstanceValue(wfHostTypeName) },
// Something here to identify the WorkflowIdentity ??
}
};
store.DefaultInstanceOwner = store.Execute(ownerHandle, ownerCommand, TimeSpan.FromSeconds(30)).InstanceOwner;
return ownerHandle;
}
private static void WaitForRunnableInstance(InstanceStore store, InstanceHandle ownerHandle)
{
// Only fires when using default Identity
IEnumerable<InstancePersistenceEvent> events = store.WaitForEvents(ownerHandle, TimeSpan.MaxValue);
bool foundRunnable = false;
// Loop through the persistence events looking for the HasRunnableWorkflow event (in this sample, it corresponds with
// the workflow instance whose timer has expired and is ready to be resumed by the host).
foreach (InstancePersistenceEvent persistenceEvent in events)
{
if (persistenceEvent.Equals(HasRunnableWorkflowEvent.Value))
{
foundRunnable = true;
break;
}
}
if (!foundRunnable)
{
throw new ApplicationException("Unexpected: No runnable instances found in the instance store");
}
}

Categories