This project has moved. For the latest updates, please go here.

Workflows

Workflows are largely inspired by Rob Eisenberg's Caliburn framework. This is not the exact implementation used there - it is far more lightweight. It is an implementation of co-routines that allow you to fire asynchronous processes in a sequential fashion. This improves the readability of code and provides more flexibility for managing long running processes.

The IWorkflow interface defines:

public interface IWorkflow
{
    void Invoke();
    Action Invoked { get; set; }
}

The Invoke() method is called to start the unit of work. When the unit is completed, it should call Invoked. A WorkflowController is provided and handles setting the Invoked property; this should not be set by you.

A few workflow implementations exist:

WorkflowAction defines a generic action that can either finish immediately (Jounce will call Invoked for you) or is long running (you call Invoked when done).
WorkflowBackgroundWorker launches a background worker and waits for it to finish.
WorkflowDelay will wait a specified period of time before continuing the workflow.
WorkflowEvent wraps an existing event. You can type it as WorkflowEvent<T>. You specify how the event is triggered and how to wire in the completed handler, and Jounce does the rest - it will fire the event, wait for it to complete, then store the result.
WorkflowRoutedEvent does the same with routed events.

Here is an example workflow - it will not block the UI thread, but each workflow item will run asynchronously and the workflow will wait for it to finish, then continue at the next statement after the yield.

public MainPage()
        {
            InitializeComponent();
            WorkflowController.Begin(Workflow(),
                                     ex => JounceHelper.ExecuteOnUI(() => MessageBox.Show(ex.Message)));
        }

        private IEnumerable<IWorkflow> Workflow()
        {
            Button.Visibility = Visibility.Visible;
            Text.Text = "Initializing...";
            Button.Content = "Not Yet";
            Button.IsEnabled = false;

            // wait two seconds
            yield return new WorkflowDelay(TimeSpan.FromSeconds(2));

            Button.IsEnabled = true;
            Text.Text = "First Click";
            Button.Content = "Click Me!";

            yield return new WorkflowRoutedEvent(() => { }, h => Button.Click += h, h => Button.Click -= h);

            Text.Text = "Now we'll start counting!";
            Button.Content = "Click Me!";

            yield return new WorkflowRoutedEvent(() => { }, h => Button.Click += h, h => Button.Click -= h);

            Button.IsEnabled = false;
            Button.Content = "Counting...";

            yield return new WorkflowBackgroundWorker(_BackgroundWork, _BackgroundProgress);

            Text.Text = "We're Done.";
            Button.Visibility = Visibility.Collapsed;
        }

        private void _BackgroundProgress(BackgroundWorker arg1, ProgressChangedEventArgs arg2)
        {
            Text.Text = string.Format("{0}% complete, counted to: {1}", arg2.ProgressPercentage, (int) arg2.UserState);
        }

        private static void _BackgroundWork(BackgroundWorker obj)
        {
            for (var x = 0; x < int.MaxValue; x++)
            {
                var mod = (int)Math.Ceiling(int.MaxValue/100);

                if (x%mod == 0)
                {
                    obj.ReportProgress(x/mod, x);
                }
            }
        }

Last edited Mar 27, 2011 at 4:26 PM by jeremylikness, version 2

Comments

mokdes Mar 14, 2013 at 6:17 PM 
Hi Jeremy,

I have a case where my action will fire an asynchronous operation and registers for the completed event so that Invoked() will be called. the issue is that if I have any error in the completed event args than I cannot report it. I'm using for this WorkflowAction class. can WorkflowBackgroundWorker solve this ?

Here is the first yield statement of my workflow:

private IEnumerable<IWorkflow> GetInitializationWorkflow()
{
loadApplicationConfigurationFlow = new WorkflowAction(() =>
{
string uri = ApplicationParametersService.InitParameters["ConfigurationFile"];
ConfigurationsService.LoadConfigurationsCompleted += (sender, args) =>
{
if (args.Error == null && !args.Cancelled)
{
Debug.Assert(ConfigurationsService.ApplicationConfigurations == args.Result, "Most recent configurations should be equal");
}
else
{
// I need to report this issue to the WorkflowController
InitializationError = "Failed to load application configurations.";
// throw new Exception("Failed to load application configurations.", args.Error); // This causes an unhandled exception.
//EventAggregator.Publish<ViewNavigationArgs>(new ViewNavigationArgs(typeof(MainPageInitializationFailed)));
}
loadApplicationConfigurationFlow.Invoked();
};
ConfigurationsService.LoadConfigurationsAsync(new Uri(uri+1, UriKind.Absolute), uri);
});

yield return loadApplicationConfigurationFlow;
......

Regards,

tymberwyld Sep 30, 2011 at 5:09 PM 
Hi Jeremy, do you have any examples of how I can use the Workflows to execute a DomainService call from Silverlight and "wait" for it to finish before returning the "IEnumerable<TEntity>" list contained in the LoadOperation? I'm just having a tough time wrapping my head around it.