views:

1218

answers:

6

Howdy,

I have been bitten by a poorly architected solution. It is not thread safe!

I have several shared classes and members in the solution, and during development all was cool...
BizTalk has sunk my battle ship.

We are using a custom BizTalk Adapter to call my assemblies. The Adapter is calling my code and running things in parallel, so I assume it is using multiple threads all under the same AppDomain.

What I would like to do is make my code run under its own AppDomain so the shared problems I have will not muck with each other.

I have a very simple class that the BizTalk adapter is instantiating then running a Process() method.

I would like to create a new AppDomain inside my Process() method, so each time BizTalk spins another thread, it will have its own version of the static classes and methods.

BizTalkAdapter Code:

  // this is inside the BizTalkAdapter and it is calling the Loader class //
  private void SendMessage(IBaseMessage message, TransactionalTransmitProperties properties)
    {

        Stream strm = message.BodyPart.GetOriginalDataStream();
        string connectionString = properties.ConnectionString;
        string msgFileName = message.Context.Read("ReceivedFileName", "http://schemas.microsoft.com/BizTalk/2003/file-properties") as string;


        Loader loader = new Loader(strm, msgFileName, connectionString);
        loader.Process();

        EventLog.WriteEntry("Loader", "Successfully processed: " + msgFileName);

    }

This is the class BizTalk Calls:

public class Loader
{

    private string connectionString;
    private string fileName;
    private Stream stream;
    private DataFile dataFile;

    public Loader(Stream stream, string fileName, string connectionString)
    {
        this.connectionString = connectionString;
        this.fileName = fileName;
        this.stream = stream;
    }  

    public void Process()
    {

        //*****  Create AppDomain HERE *****
        // run following code entirely under that domain
        dataFile = new DataFile(aredStream, fileName, connectionString);
        dataFile.ParseFile();
        dataFile.Save();
        // get rid of the AppDomain here...

    }

}

FYI: The Loader class is in a seperate DLL from the dataFile class.

Any help would be appreciated. I will continue to working on making the code Thread-Safe, but I feel like this could be the "simple" answer.

If anyone has any other thought, please throw in.

Thank you,
Keith

A: 

Why not just put a lock around the code you want to execute sequentially? It will be a bottleneck, but it should work in a multithreaded environment.

public class Loader
{
    private static object SyncRoot = new object();
    private string connectionString;
    private string fileName;
    private Stream stream;
    private DataFile dataFile;

    public Loader(Stream stream, string fileName, string connectionString)
    {
        this.connectionString = connectionString;
        this.fileName = fileName;
        this.stream = stream;
    }  

    public void Process()
    {

        lock(SyncRoot) {
            dataFile = new DataFile(aredStream, fileName, connectionString);
            dataFile.ParseFile();
           dataFile.Save();
        }

    }

}
tvanfosson
I thought of that one.. The lock will cause the other threads to wait, but the shared classes are not cleaned up. They stay around causing headaches.
Keith Sirmons
+1  A: 

Using app domains you could do something like this:

public class Loader
{

    private string connectionString;
    private string fileName;
    private Stream stream;
    private DataFile dataFile;

    public Loader(Stream stream, string fileName, string connectionString)
    {
        this.connectionString = connectionString;
        this.fileName = fileName;
        this.stream = stream;
    }  

    public void Process()
    {
        //*****  Create AppDomain HERE *****
        string threadID = Thread.CurrentThread.ManagedThreadId.ToString();
        AppDomain appDomain = AppDomain.CreateDomain(threadID);

        DataFile dataFile = 
            (DataFile) appDomain.CreateInstanceAndUnwrap(
               "<DataFile AssemblyName>", 
               "DataFile", 
               true, 
               BindingFlags.Default,
               null,
               new object[] 
               { 
                aredstream, 
                filename, 
                connectionString 
               },
               null,
               null,
               null);
        dataFile.ParseFile();
        dataFile.Save();

        appDomain.Unload(threadID);       
    }
}
Kev
Kev, If you are using the threadID as the Domain Name, could a second use of this class by the same thread create the same Domain Name? If it did, would the two AppDomains be different from each other with the same name? Keith
Keith Sirmons
I guess since you are unloading the domain, it would be ok. The static's I'm worried about would be GC'ed at that point.. Right?
Keith Sirmons
string threadID = Thread.CurrentThread.ManagedThreadId.ToString();
Keith Sirmons
If there was a second use of the Loader class and Process method by the same thread, access would be serialised because the thread can't be in two places at once or so to speak. Thanks for the correction as well.
Kev
All the statics used by the DataFile class would be local to the app domain and would also be cleaned up when the app domain is torn down.
Kev
Works like a charm. Thank you.Keith
Keith Sirmons
Make sure you mark any Custom Exceptions [Serializeable] and implement ISerializable with the special constructor to Deserialize.
Keith Sirmons
+1  A: 

Which bit, exactly, is being a pain in terms of thread safety? I can't see any static state nor singletons - and there seems to be appropriate "new" objects... am I being blind?

So what is the symptom you are seeing...

An AppDomain answer will be (relatively) slow. As part of a middleware-backed system this might be OK (i.e. the "relatively" is in the same ball-park).

If you do have some static state somewhere, another option that sometimes works is [ThreadStatic] - which the runtime interprets as "this static field is unique per thread". You need to be careful with initialization, though - the static constructor on thread A might assign a field, but then thread B would see a null/0/etc.

Marc Gravell
I have some static classes below the dataFile, inside the parse logic and save logic. I will take a look at this ThreadStatic and see if it may help. Thank you. Keith
Keith Sirmons
A: 

If you have shared statics that are conflicting with each other, then you might want to try adding [ThreadStatic] attribute to them. This will make them local to each thread. That may solve your problem in the short term. A correct solution would be to simply rearchitect your stuff to be thread-safe.

dviljoen
A: 

Just for completeness.

I did find that if I marked the send adapter as "Ordered Delivery" in the "Transport Advanced Options" dialog I was able to avoid the multi-thread issues I was having.

I figure this is another possible answer to my problem, but not necessarily to the question.

Keith Sirmons
A: 

Creating and tearing down an appdomain for each call - I take it you're not worried about performance on this one?

Ideally you should change the called code to be threadsafe.

Ben