Freigeben über


Synchronous execution in WF

My previous post points to those of you who want a first look at WF 4.0.

This post focuses more on how to get one of the best Async Runtimes to get executed in sync(shooting myself in the foot here). This was inspired by a question from a friend of mine who wanted to play with the gory stuff like TLS and other unmentionables. For people already working in WF 3.X you would be familiar with the Manual Workflow Sheduler Service. If not then to sum it up its a way to run your activity synchronosly.

That being said I am not sure on how familiar you are with the little known but very cool SynchronizationContext that got introduced with 2.0. WF leverages this to solve this fundamental problem.

I would like to first explain using the default sync context in a winform app to showcase how to avoid invoke calls when updating your control text using a workflow.

using System.Windows.Forms;

using System.Threading;

using System.WorkflowModel;

namespace WinformTest

{

    class Program

    {

        static void Main(string[] args)

        {

            Application.Run(new MyForm());

        }

        class MyForm : Form

        {

            SynchronizationContext ctx;

            Button button;

            public MyForm()

            {

                ctx = SynchronizationContext.Current;

                button = new Button();

                button.Click += new System.EventHandler(b_Click);

                this.Controls.Add(button);

            }

            void b_Click(object sender, System.EventArgs e)

            {

                ChangeText we = new ChangeText();

                we.form = this;

                WorkflowInstance instance = WorkflowInstance.Create(we, this.ctx);

                instance.Resume();

            }

            class ChangeText : WorkflowElement

            {

                public MyForm form;

                protected override void Execute(ActivityExecutionContext context)

                {

                    form.button.Text = "Changed";

                }

            }

        }

    }

}

Now where did that control.Invoke call disapper. Thats where SynchronizationContext kicks in.
To show a simple sync context for synchronous execution you could just pass in your own Sync context as below.

Here the idea is to set a value on the TLS and execute a workflow with and without the context and showcase what value comes of the TLS in each case.

using System;

using System.Threading;

using System.WorkflowModel;

namespace SyncContextSample

{

    class Program

    {

        static void Main(string[] args)

        {

            DataItem.id = Thread.CurrentThread.ManagedThreadId.ToString();

            DataItem.DisplayData();

            ExecuteWorkflow();

            Console.ReadLine();

        }

        private static void ExecuteWorkflow()

        {

            DataItem.id = Thread.CurrentThread.ManagedThreadId.ToString();

            DataItem.DisplayData();

            SynchronizationContext context = new SynchronousSynchronizationContext();

            WorkflowInstance instance = WorkflowInstance.Create(CreateRootActivity(), context);

            instance.Resume();

            WorkflowInstance instance2 = WorkflowInstance.Create(CreateRootActivity());

           instance2.Resume();

        }

        public static WorkflowElement CreateRootActivity()

        {

            return new DipslayTLS();

        }

    }

    public class DipslayTLS : WorkflowElement

    {

        protected override void Execute(ActivityExecutionContext context)

        {

            Console.WriteLine("Executing Activity");

            DataItem.DisplayData();

        }

    }

    public class DataItem

    {

        [ThreadStatic]

        public static string id;

        public static void DisplayData()

        {

            Console.WriteLine("Thread: " + System.Threading.Thread.CurrentThread.ManagedThreadId + " has data: " + id);

        }

    }

   

    public class SynchronousSynchronizationContext : SynchronizationContext

    {

        public SynchronousSynchronizationContext()

            : base()

        {}

        public override void Post(SendOrPostCallback callback, object state)

        {

            callback(state);

        }

        public override void Send(SendOrPostCallback callback, object state)

        {

            callback(state);

        }

    }

}

 

You would get something like this

Thread: 1 has data: 1
Thread: 1 has data: 1
Executing Activity
Thread: 1 has data: 1
Executing Activity
Thread: 3 has data:

Just as note this is more of rare case and should be used only when you know you need it cause you loose all the benefits of going totally async and taking advantage of the WF 4.0 runtime robustness.