Rebus, exception when creating AppDomain / Instanc

2019-07-12 20:40发布

问题:

We have a problem with the new (async) version of Rebus that didn’t exist with the older version.

When handling rebus message and trying to create AppDomain and Instance to run plugin code dynamically, it always give me an exception. To make the example as simple as possible, I made a Test method:

public static void Test()
{
    AppDomain ad = AppDomain.CreateDomain("Test");
    Loader loader = (Loader)ad.CreateInstanceAndUnwrap(typeof(Loader).Assembly.FullName, typeof(Loader).FullName);
}

class Loader : MarshalByRefObject
{
}

When I’m calling the method from ‘normal’ code it works but when I’m calling it from (async) Rebus message Handle method, it gives an exception:

System.Runtime.Serialization.SerializationException was caught
HResult=-2146233076 Message=Type 'Rebus.Transport.DefaultTransactionContext' in assembly 'Rebus, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable. Source=mscorlib StackTrace: at System.AppDomain.CreateInstanceAndUnwrap(String assemblyName, String typeName) at App.Bus.MessageParse.Process.Test() in d:\Project\App.Bus.MessageParser\Process.cs:line 45 at App.Bus.MessageParse.Process.d__0.MoveNext() in d:\Project\App.Bus.MessageParser\Process.cs:line 28 InnerException:

Any idea about the problem?

回答1:

Rebus stores its transaction context in AmbientTransactionContext.Current, which is backed by the thread's logical call context, which automatically flows to continuations when you await something.

It flows to created appdomains as well, apparently ;)

I could mark the DefaultTransactionContext as serializable, but I fear that you would then just get an exception telling you that the items in the transaction context's dictionary are not serializable.

There is no way I can make the transaction context truly serializable and guarantee that it would work, so - if you need to create an appdomain in a message handler - I suggest you temporarily remove the ambient transaction context - just remember to put it back again :)

Something like the following should do the trick:

public async Task Handle(SomeMessage message)
{
    var transactionContext = AmbientTransactionContext.Current;
    AmbientTransactionContext.Current = null;
    try
    {
        JuggleWithAppDomainsInHere();
    }
    finally
    {
        AmbientTransactionContext.Current = transactionContext;
    }
}

If it's a common pattern in your app that you do stuff with appdomains, I suggest you wrap the "removing-and-restoring-of-the-ambient-Rebus-transaction" in something IDisposable, so you can

using(new DismantleAmbientRebusStuff())
{
    JuggleWithAppDomainsInHere();
}