So, before I get into that beast, I wanted to at least share a note on Thread Safety and using ExternalDataExchange interfaces.
The first important thing to note is ExternalDataExchange services live as singletons in the workflow runtime. However, unlike our powerful new toy WCF, you are not guarunteed a singleton with proper thread blocking.
What does that mean?
If you haven't heard of Free Threading, that's pretty much what this is. Think about when you add an ExternalDataExchange service to the workflow runtime. When you do it in code, it would look something to the effect of.
using (WorkflowRuntime runtime = new WorkflowRuntime("WorkflowRuntimeConfigurationPT"))
ExternalDataExchangeService dataExchangeService = new ExternalDataExchangeService();
MyWorkflowService service1 = new MyWorkflowService();
The important thing to note above is the construction of our Workflow service that we will use to perform whatever function we wish. From our previous example it could be to update the database with information from the workflow state about an order, such as adding or removing an item.
We construct an instance of that service and let the runtime and dataexchange take that over. But keep in mind, we constructed the instance (instead of referencing a type in which case the runtime would create instances as needed).
Often times we find ourselves being a bit lazy and not thinking about what exactly is going to be accessing instances of our classes. Especially when we are not necessarily thinking about multi-threaded programming (I think WCF spoiled this for us). After all, the application that I wrote doesn't use the ThreadPool or anything else touching System.Threading.
But Workflow does.
What can happen is two workflows may reach a state in which they call the same method on the same interface. Because you can only have only 1 instance of any interface loaded into a single runtime this can cause both threads from each workflow instance to "cross paths". When debugging, some may notice this as "code jumping", where your running F10, and then all of a sudden your 3 lines before you were and not paying attention to your thread monitor window. ;)
What can happen is the same thing in any free threaded application. Always remember the basics of computers, a processor can only process a single instruction at a time. And a line of code does not necessarily equal a single instruction (no where close ;)) so, the Thread Scheduler deep inside the OS will swap out different sequences of instructions from different processes at different times, not necessarily completing a logical set of instructions as we humans see it.
Sometimes this doesn't matter, however in most instances you may be using a class level or even method level variable, in which case if both threads are accessing the method at the same time, thread A sets a variable to 1 and thread B sets it to 2. When thread A gets scheduled its next instruction, values that it originally had may not be there, leading to "weird" or "random" results and or exceptions. Also, the most common example is that both thread A and B create a race condition where neither can exit a loop because of what the other one is doing.
I will not go into a huge speech of the importance of thread safety and how to do it, there are much better examples out there than what I will give you. The key to take from this post is always think when developing your workflow services, "two workflow may access this code at the exact same time, what have I done to protect myself". The easy thing to do is use of the lock statement. This will save you hours of headache later.