WF4 - Resuming workflows with delay - c#

I'm trying to automatically resume workflows when the delay is over (using a delay activity)
This is done automatically if you use WorkflowServiceHost, but I'm using WorkflowApplication.
There are a few codes on the internet that handle this, here is a sample
I'm using WorkflowApplication to achieve these goals:
A user create a task, and this task follow a workflow
User design it's own workflow using a rehosted designer, and his workflow definition is saved in the database.
Each workflow definitions can have different versions saved in the database to allow the user to modify its workflows, and the code use the correct version when an action is done on a task and it needs to resume bookmarks
A task can be started using a workflow, so in the task table I store which workflow definition and which version is used for this task.
The problem I've now is when using delay activity
Using the code linked above, when a runnable instance is detected I need to create a WorkflowApplication and use the LoadRunnableInstance method to load the workflow:
if (hasRunnableWorkflows)
{
Console.WriteLine("Found runnable workflows");
WorkflowApplication app = new WorkflowApplication(...);
app.InstanceStore = store;
app.LoadRunnableInstance();
}
Problem is:
To call LoadRunnableInstance, I need to create the WorkflowApplication and so I need to give the root activity to the WorkflowApplication CTor
To create the the root activity, I need to know which workflow (& version) I need to load from database
To know which workflow to load, I need the task ID
To know the task ID, I need to know the Workflow ID, which is only available after the call to LoadRunnableInstance
So ... I need to break this loop :)
Does someone know a way to have the Workflow ID before loading the workflow ?

The ControllingWorkflowApplications sample from the Microsoft wcf and wf samples shows that the system has to maintain it's list of workflow id's to know what workflows to load from the instance store. In that example, they also keep the path the workflow xaml file which should be able to handle your versions. That example isn't the easiest to follow but an extension to the WorkflowAppication that inherits from PeristenceIOParticipant is added to WorkflowApplication. This class extracts and defines the additional data that will be saved with the workflow. For your purposes that would be the id.
instance.Extensions.Add(() => new WorkflowDefinitionExtension(originalPath, connectionString)); //inherits from PeristenceIOParticipant

This worked for me.
When the Delay activity is hit by the workflow runtime, event PersistableIdle is fired and if it returns PersistableIdleAction.Persist
then the Delay activity will be automatically resumed. If event handler returns PersistableIdleAction.Unload then the Delay activity won't be resumed
automatically.
Here is the Sample,
Workflow Definition:
Variable<string> name = new Variable<string>
{
Name = "name"
};
wf = new Sequence
{
Variables =
{
name
},
Activities =
{
new WriteLine()
{
Text = "Workflow Triggered"
},
new Delay()
{
Duration = TimeSpan.FromSeconds(10)
},
new WriteLine()
{
Text = "Activity1 Completed"
},
}
};
WorkflowApplication:
WorkflowApplication wfApp = new WorkflowApplication(wf);
wfApp.PersistableIdle = delegate (WorkflowApplicationIdleEventArgs e)
{
return PersistableIdleAction.Persist;
};
// Start the workflow.
wfApp.Run();

Related

Is there a way to retrieve an existing NSUrlSession/cancel its task?

I am creating an NSUrlSession for a background upload using a unique identifier.
Is there a way, say after closing and reopening the app, to retrieve that NSUrlSession and cancel the upload task in case it has not been processed yet?
I tried simply recreating the NSUrlSession using the same identifier to check whether it still contains the upload task, however it does not even allow me to create this session, throwing an exception like "A background URLSession with identifier ... already exists", which is unsurprising as documentation explicitly says that a session identifier must be unique.
I am trying to do this with Xamarin.Forms 2.3.4.270 in an iOS platform project.
Turns out I was on the right track. The error message "A background URLSession with identifier ... already exists" actually seems to be more of a warning, but there is not actually an exception thrown (the exception I had did not actually come from duplicate session creation).
So, you can in fact reattach to an existing NSUrlSession and will find the contained tasks still present, even after restarting the app. Just create a new configuration with the same identifier, use that to create a new session, ignore the warning that's printed out, and go on from there.
I am not sure if this is recommended for production use, but it works fine for my needs.
private async Task EnqueueUploadInternal(string uploadId)
{
NSUrlSessionConfiguration configuration = NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration(uploadId);
INSUrlSessionDelegate urlSessionDelegate = (...);
NSUrlSession session = NSUrlSession.FromConfiguration(configuration, urlSessionDelegate, new NSOperationQueue());
NSUrlSessionUploadTask uploadTask = await (...);
uploadTask.Resume();
}
private async Task CancelUploadInternal(string uploadId)
{
NSUrlSessionConfiguration configuration = NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration(uploadId);
NSUrlSession session = NSUrlSession.FromConfiguration(configuration); // this will print a warning
NSUrlSessionTask[] tasks = await session.GetAllTasksAsync();
foreach (NSUrlSessionTask task in tasks)
task.Cancel();
}

Registering a background task in WinRT

I'm trying to register a background task and I'm getting some strange behaviour. I appears that the task itself is registering and firing at the right time; however, when it does fire it's closing my program down (with no error).
I suspect that the reason is linked to the fact that the program is not asking me is I want to allow a background task to run when I launch. I have created a declaration for the background task as a system event, and am registering like this from the App.Xaml.cs:
var builder = new BackgroundTaskBuilder();
builder.Name = "NewTask";
builder.TaskEntryPoint = "ConnectionMonitor.CheckInternet";
builder.SetTrigger(new SystemTrigger(SystemTriggerType.InternetAvailable, false));
BackgroundTaskRegistration task = builder.Register();
So, I believe my question is: why would it not ask me for permission to run a background task (which I assume will give me the answer to my main problem of why it is crashing)?
I'm not sure whether it matters, but this is a Windows Universal app (the app.xaml.cs above is in the Shared project.
The task looks like this:
BackgroundTaskDeferral _deferral = taskInstance.GetDeferral();
bool newConnected = IsConnected();
if (connected != newConnected)
{
connected = newConnected;
var notifier = ToastNotificationManager.CreateToastNotifier();
var template = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02);
var element = template.GetElementsByTagName("text")[0];
element.AppendChild(template.CreateTextNode(connected ? "Connection available" : "Connection lost"));
var toast = new ToastNotification(template);
notifier.Show(toast);
}
_deferral.Complete();
You asked: why would it not ask me for permission to run a background task?
The answer is, unless your background task requires lock screen access, it does not require the user's permission to be registered. There is no user prompt, by design. One of the intents of this design is that it allows you to register a task from another task.
In Windows, you do not need to call BackgroundExecutionManager.RequestAccessAsync() except for lock screen access. Calling it will give you more quota but will require the user to approve the task.
In Windows Phone, calling RequestAccessAsync() is required no matter what, but never prompts the user with a UI. For this reason the logic in your Universal App can be shared but will likely have a #if to handle the registration differently, if relevant.

Attempt at an Asynchronous method is failing

I have an MVC3/.NET 4 application which uses Entity Framework (4.3.1 Code First)
I have wrapped EF into a Repository/UnitOfWork pattern as described here…
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
Typically, as it explains in the article, when I require the creation of a new record I’ve been doing this…
public ActionResult Create(Course course)
{
unitOfWork.CourseRepository.Add(course);
unitOfWork.Save();
return RedirectToAction("Index");
}
However, when more than simply saving a record to a database is required I wrap the logic into what I’ve called an IService. For example…
private ICourseService courseService;
public ActionResult Create(Course course)
{
courseService.ProcessNewCourse(course);
return RedirectToAction("Index");
}
In one of my services I have something like the following…
public void ProcessNewCourse(Course course)
{
// Save the course to the database…
unitOfWork.CourseRepository.Add(course);
unitOfWork.Save();
// Generate a PDF that email some people about the new course being created, which requires more use of the unitOfWork…
var someInformation = unitOfWork.AnotherRepository.GetStuff();
var myPdfCreator = new PdfCreator();
IEnumerable<People> people = unitOfWork.PeopleRepository.GetAllThatWantNotifiying(course);
foreach(var person in people)
{
var message = “Hi ” + person.FullName;
var attachment = myPdfCreator.CreatePdf();
etc...
smtpClient.Send();
}
}
The above isn’t the actual code (my app has nothing to do with courses, I’m using view models, and I have separated the PDF creation and email message out into other classes) but the gist of what is going on is as above!
My problem is that the generation of the PDF and emailing it out is taking some time. The user just needs to know that the record has been saved to the database so I thought I would put the code below the unitOfWork.Save(); into an asynchronous method. The user can then be redirected and the server can happily take its time processing the emails, and attachments and whatever else I require it to do post save.
This is where I’m struggling.
I’ve tried a few things, the current being the following in ICourseService…
public class CourseService : ICourseService
{
private delegate void NotifyDelegate(Course course);
private NotifyDelegate notifyDelegate;
public CourseService()
{
notifyDelegate = new NotifyDelegate(this.Notify);
}
public void ProcessNewCourse(Course course)
{
// Save the course to the database…
unitOfWork.CourseRepository.Add(course);
unitOfWork.Save();
notifyDelegate.BeginInvoke(course);
}
private void Notify(Course course)
{
// All the stuff under unitOfWork.Save(); moved here.
}
}
My Questions/Problems
I’m randomly getting the error: "There is already an open DataReader associated with this Command which must be closed first." in the Notify() method.
Is it something to do with the fact that I’m trying to share the unitOrWork and therefore a dbContext across threads?
If so, can someone be kind enough to explain why this is a problem?
Should I be giving a new instance of unitOfWork to the Notify method?
Am I using the right patterns/classes to invoke the method asynchronously? Or should I be using something along the lines of....
new System.Threading.Tasks.Task(() => { Notify(course); }).Start();
I must say I've become very confused with the terms asynchronous, parallel, and concurrent!!
Any links to articles (c# async for idiots) would be appreciated!!
Many thanks.
UPDATE:
A little more digging got me to this SO page: https://stackoverflow.com/a/5491978/192999 which says...
"Be aware though that EF contexts are not thread safe, i.e. you cannot use the same context in more than one thread."
...so am I trying to achieve the impossible? Does this mean I should be creating a new IUnitOfWork instance for my new thread?
You could create a polling background thread that does the lengthy operation separately from your main flow. This thread could scan the database for new items (or items marked to process). This solution is pretty simple and ensures that jobs get done even if you application crashes (it will be picked up when the polling thread is started again).
You could also use a Synchronised Queue if it's not terrible if the request is 'lost', in the case your application crashes after the doc is requested and before it's generated/sent.
One thing is almost sure - as rikitikitik said - you will need to use a new unit of work, which means a separate transaction.
You could also look at Best threading queue example / best practice .

Better approach to queuing reports

Say I have a windows service that runs a method to generate reports.
For example say I have a Reports table that has the report path, status, name of report, parameters etc.
When a user clicks the generate report button a new entry will be added to the table with a status of queued. The service will take queued reports, generate the report, update the status and set the path of the completed report.
The 2 ways I can think of doing this is to either poll the table for queued reports (something like):
TimerCallback callback = new TimerCallback(RunQuery);
this.QueryTimer = new Timer(callback, null, 1000, 10000);
public void RunQuery(object obj)
{
//find reports with status of queued,
//loop through them and generate reports
}
Or create a file with the ReportId and use a FileSystemWatcher to determine which reports to run (something like):
private void FileSystemWatcher1_Created(object sender, System.IO.FileSystemEventArgs e)
{
ReportStack.Push(e.FullPath);
Thread TR = new Thread(RunQuery);
TR.Start();
}
public void RunQuery()
{
string filePath = Convert.ToString(ReportStack.Pop());
GenerateReport(filePath);
...
}
A downside to the 1st method is that I need to specify a poll time and reports don't generate instantly as they are queued. A downside to the second method is the small hassle of creating and deleting files, setting up permission for the app to write to a shared folder etc.
Is there a way to get the service to automatically kick off reports as they are placed into the report table (something like a DatabaseTableWatcher!) or another better way to do this?
SQL triggers or Message Queuing
http://msdn.microsoft.com/en-us/library/ms189799.aspx
So when a record is inserted into the table you can fire off an action such as calling a sproc.
Personally I like your first polling approach.
How about using Microsoft Message Queuing (MSMQ)? MSMQ creates and manages message queues at the operating system level. It seems a perfect match for your application.

A UI over Windows Workflow

Are there any build in UI capabilities when using Windows Workflow..
Lets say I have a workflow that takes an hour to run where different activities are happening all the time. While it's running I want to see what activity is currently active, what activities have already ran etc..
Do I have to code this UI myself or does WF have built in features that graphically show the status etc of the workflow?
To find out which state a workflow is in, I subscribe to the WorkflowIdled event and do something like this:
private delegate void UpdateDelegate();
void workflowRuntime_WorkflowIdled(object sender, WorkflowEventArgs e)
{
StateMachineWorkflowInstance stateMachineInstance = new StateMachineWorkflowInstance(MyManager.WorkflowRuntime, MyInstance.Id);
UpdateDelegate LclUpdateDelgate = delegate()
{
// Update the workflow state on the form thread
if (stateMachineInstance.CurrentState != null)
LabelWorkflowState.Text = stateMachineInstance.CurrentStateName;
else
LabelWorkflowState.Text = "";
};
this.Invoke(LclUpdateDelgate);
}
There is no built in UI.
But you can create one, either by subscribing to events on the WorflowInstance (see other answer), or by using the Tracking Service.
The former is simple to set up for a quick solution but the latter will work with multiple host processes and long running (unloaded) workflow instances.
Check out the this code sample over at MSDN:
http://msdn.microsoft.com/en-us/library/ms741706.aspx

Categories