.net 为什么跨AppDomain调用后Transaction.Current变为空?
考虑下面的小程序,它简单地创建一个.net 为什么跨AppDomain调用后Transaction.Current变为空?,.net,appdomain,remoting,transactionscope,system.transactions,.net,Appdomain,Remoting,Transactionscope,System.transactions,考虑下面的小程序,它简单地创建一个TransactionScope,打印Transaction.Current,调用另一个AppDomain中的一个方法(执行需要一段时间),然后在返回时打印Transaction.Current using System; using System.Linq; using System.Runtime.Remoting.Lifetime; using System.Threading; using System.Transactions; namespace
TransactionScope
,打印Transaction.Current
,调用另一个AppDomain中的一个方法(执行需要一段时间),然后在返回时打印Transaction.Current
using System;
using System.Linq;
using System.Runtime.Remoting.Lifetime;
using System.Threading;
using System.Transactions;
namespace TransactionScopeFlowTest
{
class Program
{
static void Main(string[] args)
{
// These times are just to generate the error faster. Normally the initial lease is 5 minutes, meaning the method call
// would have to take 5 minutes to occur, so we speed it up here for demonstration purposes.
LifetimeServices.LeaseManagerPollTime = TimeSpan.FromSeconds(1);
LifetimeServices.LeaseTime = TimeSpan.FromSeconds(1);
LifetimeServices.RenewOnCallTime = TimeSpan.FromSeconds(1);
AppDomain domain = AppDomain.CreateDomain("Temp", null, AppDomain.CurrentDomain.SetupInformation);
using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
Console.WriteLine($"Transaction Before Call = {Transaction.Current?.TransactionInformation?.LocalIdentifier?.ToString() ?? "<null>"}");
domain.DoCallBack(AppDomainCallback);
Console.WriteLine($"Transaction After Call = {Transaction.Current?.TransactionInformation?.LocalIdentifier?.ToString() ?? "<null>"}");
scope.Complete();
}
AppDomain.Unload(domain);
}
public static void AppDomainCallback()
{
Thread.Sleep(3000);
}
}
}
使用系统;
使用System.Linq;
使用System.Runtime.Remoting.Lifetime;
使用系统线程;
使用系统事务;
命名空间TransactionScopeFlowTest
{
班级计划
{
静态void Main(字符串[]参数)
{
//这些时间只是为了更快地生成错误。通常初始租约是5分钟,这意味着方法调用
//将需要5分钟才能发生,因此为了演示,我们在这里加快了速度。
LifetimeServices.LeaseManagerPollTime=TimeSpan.FromSeconds(1);
LifetimeServices.LeaseTime=TimeSpan.FromSeconds(1);
LifetimeServices.RenewOnCallTime=TimeSpan.FromSeconds(1);
AppDomain域=AppDomain.CreateDomain(“Temp”,null,AppDomain.CurrentDomain.SetupInformation);
使用(TransactionScope作用域=新TransactionScope(TransactionScope异步流选项.Enabled))
{
Console.WriteLine($“调用前的事务={Transaction.Current?.TransactionInformation?.LocalIdentifier?.ToString()??“}”);
domain.DoCallBack(AppDomainCallback);
Console.WriteLine($“调用后的事务={Transaction.Current?.TransactionInformation?.LocalIdentifier?.ToString()??“}”);
scope.Complete();
}
卸载(域);
}
公共静态void AppDomainCallback()
{
睡眠(3000);
}
}
}
出乎意料的是,程序生成了以下输出:
Transaction Before Call = 1f980219-2583-4796-8d6d-256a6f100698:1
Transaction After Call = <null>
调用前事务=1f980219-2583-4796-8d6d-256a6f100698:1
调用后的事务=
如果我将TransactionScope
的ctor中的TransactionScopeAsyncFlowOption
更改为TransactionScopeAsyncFlowOption.Suppress
,则该事务在调用后保留
我怀疑跨逻辑上下文的事务作用域流是由CallContext处理的,CallContext在远程处理调用中传播,使用的键继承了MarshalByRefObject
,并且由于它们没有向任何ISponsor
注册,代理将在初始租用时间后断开连接。然后,一旦我们从调用返回,逻辑调用上下文就会与原始上下文合并,这意味着事务不再存在
我正在寻找一种方法来避免这个问题,而且如果这被认为是.NET中的一个bug呢?你已经说了。参考资料显示,当您需要AsyncFlow时,事务状态使用MarshallByRefObject ContextKey类:事实是,事务不会按设计在AppDomains之间流动:“对于异步流场景,我们不应该允许事务在应用程序域之间流动”。没有“合并回”。存储为弱引用的密钥刚刚消失,因此事务似乎从未存在过。我不确定这是一只虫子。我看不到一个简单的解决方案,但向Microsoft报告(或者停止对很久以前设计的糟糕TransactionScope进行花哨的处理,甚至在.NET存在之前:-)我知道该事务不会在调用中流动,并且我不会在其他AppDomain中使用该事务。奇怪的是,在调用后,事务在原始AppDomain中消失。因为ContextKey实例在域之间应该是相同的,一旦它在临时域中由于租约时间而被终止,其他域也会终止。因为它最初存储在一个弱引用字典中:对于初始域,它好像从未存在过。这个问题在