交易范围超时时间为10分钟。

41

我在C#中有一个长时间运行的TransactionScope。我告诉了作用域它应该有一个长时间跨度,但是我仍然遇到了超时。这可能是什么原因?

TransactionOptions transactionOptions = new TransactionOptions();
transactionOptions.IsolationLevel = IsolationLevel.ReadCommitted;
transactionOptions.Timeout = TimeSpan.MaxValue;
using (var ts = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
{ 
    DoLongCode();
}

1
正如@nonnb所提到的,您需要在SQL调用或对象上下文中设置超时时间。 - Grant H.
7
哎呀;你为什么想要一个持续10分钟的“TransactionScope”?如果我有一个持续时间超过几秒钟的“TransactionScope”,我会变得非常焦虑。长时间运行的事务可能会严重影响所有其他调用者......您还需要考虑提交/回滚成本;在许多平台上,“回滚”付出代价(提交很便宜);如果在这10分钟内完成了大量的工作,回滚可能会是致命的。 - Marc Gravell
这确实不是一个理想的情况,但我现在必须处理它。这是一个性能非常糟糕的弱进程。 - Patrick
@MarcGravell 因为有时候你必须移动大量的数据。 - Suncat2000
10个回答

44

要允许交易超过10分钟而无需更改machine.config,请使用以下代码

    private void SetTransactionManagerField(string fieldName, object value)
    {
        typeof(TransactionManager).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, value);
    }

    public TransactionScope CreateTransactionScope(TimeSpan timeout)
    {
        // or for netcore / .net5+ use these names instead:
        //    s_cachedMaxTimeout
        //    s_maximumTimeout
        SetTransactionManagerField("_cachedMaxTimeout", true);
        SetTransactionManagerField("_maximumTimeout", timeout);
        return new TransactionScope(TransactionScopeOption.RequiresNew, timeout);
    }

使用方法:

using (var ts = CreateTransactionScope(TimeSpan.FromMinutes(20)))
{ 
    DoLongCode();
    ts.Complete();
}

基于这篇文章 文章中的代码原本在此处粘贴。答案中的代码现在已经重构并简化。


不是我想要实现的方式,但这是一个解决方案,对我有效(Azure WebJob) - martinoss
我非常喜欢这个解决方案,因为在许多环境中,您无法访问machine.config文件,而这个解决方案可以按预期工作。 - Josh
2
对我来说,这些字段的名称是s_cachedMaxTimeout和s_maximumTimeout,所以我已经更改了它们,尝试使用任何一个字段名称并使用有效的那个。这仍然基本上是一种hack(如原始文章中所述),但有时确实没有其他选择。 - Alan Fluka
这完美地展示了这个 hack 的问题。它很容易出现故障。在生产代码中永远不应该使用它。谁知道,在下一个版本中,这些成员可能会完全消失,那么你就会面临一个大问题。 - Gert Arnold

43
进一步澄清:
事务范围使用机器配置设置作为最大超时时间。 默认的机器超时时间为10分钟。
将机器配置设置为2小时:
      <system.transactions>
        <machineSettings maxTimeout="02:00:00"/>
      </system.transactions> 

app.config或web.config可以用于缩短超时时间,但不能用于超过机器配置的超时时间。

将app config设置为1小时:

<system.transactions>
     <defaultSettings timeout="01:00:00" />
</system.transactions>

当达到限制时,我们也没有收到任何异常,也没有跟踪或事件日志记录。

此外,TransactionScope对象具有构造函数重载,允许您指定超时时间,但我不确定如何处理它。


26

你可以在配置文件中验证maxTimeout,如果你的web.config或app.config中没有这个部分。

请验证你的machine.config文件。

<configuration> 
  <system.transactions>
    <machineSettings maxTimeout=""/>
  </system.transactions>
</configuration> 

调整该值


3
在Windows Azure中,这是无法更改的,10分钟是最大超时时间。 - schglurps
1
是的,可以覆盖 machine.config。 - Aghilas Yakoub
4
根据 MSDN 论坛上的这个帖子,你不能在 web.config 或 app.config 文件中覆盖 maxTimeout 值。http://social.msdn.microsoft.com/Forums/da-DK/windowstransactionsprogramming/thread/584b8e81-f375-4c76-8cf0-a5310455a394 - jpierson
7
@jpierson 我真的非常讨厌微软因为这个问题。仅仅因为一个应用程序需要特殊行为,我为什么要改变系统设置呢?他们曾经想过像“LocalDB”、“SQLCe”、“SQLite”这样的独占模式本地数据库吗?没有! - springy76

7

这些代码无法工作,因为你试图更改超时时间的上下文错误。

尝试将其更接近有效查询进行更改。

你应该有以下这些上下文:

    using (var txn = new TransactionScope(
                            TransactionScopeOption.Required,
                            new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted, Timeout = new TimeSpan(1,0,0) })) // 1 hour or wathever, will not affect anything
                    {

                        using (SqlConnection connection = new SqlConnection(ConnectionString))
                        {
                            int ct = connection.ConnectionTimeout // (read Only, this is the effective default timeout is 15 seconds)
                            connection.Open();

                            SqlCommand select = new SqlCommand(sql.query, connection); // bind to server
                            select.CommandTimeout = 0; // <-- here does apply infinite timeout
SqlDataReader reader = select.ExecuteReader(); // never stop

4

我通过修改 "物理文件" machine.config 来解决了这个问题。


1.您需要将文件定位在以下位置:

  • 32位操作系统:C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machine.config
  • 64位操作系统:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config

2. 您需要添加以下代码:

<system.transactions>
     <defaultSettings timeout="00:59:00" />
</system.transactions>

3

在考虑完全信任的环境下,您可以使用反射覆盖最大超时时间:

            //Get machineSettings session
            var machineSettings = (System.Transactions.Configuration.MachineSettingsSection)ConfigurationManager.GetSection("system.transactions/machineSettings");
            //Allow modifications
            var bReadOnly = (typeof(ConfigurationElement)).GetField("_bReadOnly", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            bReadOnly.SetValue(machineSettings, false);
            //Change max allowed timeout
            machineSettings.MaxTimeout = TimeSpan.MaxValue;

            using (var t = new TransactionScope(TransactionScopeOption.Required, new TimeSpan(1,0,0))) { //1 hour transaction
                //...
            }

可能的限制:它应该在任何交易之前被调用(在控制台中运行良好,但在Web应用程序中不起作用)。 - Maxim

1
如果有人想知道如何在 .NET Core 上实现这个,这就是答案。基本上,您需要更新字段名称:

_cachedMaxTimeout -> s_cachedMaxTimeout

_maximumTimeout -> s_maximumTimeout


1
不确定在哪个时刻发生了改变,但是当前的核心库已经为其提供了一个普通的设置器。System.Transactions.TransactionManager.MaximumTimeout = ...; System.Transactions.TransactionManager.DefaultTimeout = ...; - Cine

0
可以在你的项目中添加以下代码来延长交易时间。
// This is used for set the transaction timeout to 40 minutes.
Type oSystemType = typeof(global::System.Transactions.TransactionManager);
System.Reflection.FieldInfo oCachedMaxTimeout = 
                    oSystemType.GetField("_cachedMaxTimeout", 
                    System.Reflection.BindingFlags.NonPublic | 
                    System.Reflection.BindingFlags.Static);
System.Reflection.FieldInfo oMaximumTimeout = 
                    oSystemType.GetField("_maximumTimeout", 
                    System.Reflection.BindingFlags.NonPublic | 
                    System.Reflection.BindingFlags.Static);
oCachedMaxTimeout.SetValue(null, true);
oMaximumTimeout.SetValue(null, TimeSpan.FromSeconds(2400));

请注意,反射不应用于设置无法访问的成员,因为基本上你不知道自己在做什么。有合法的方法可以实现 OP 想要的功能。此外,这是对现有答案的重复。 - Gert Arnold
@GertArnold 我通常会同意设置您不拥有的类型的私有成员的值是一个非常糟糕的想法。但是,对于这种特定情况,我并不同意。 据我所知,增加事务超时的最大值的唯一官方支持方式是修改machine.config。 然而,由于实施的IT政策,我们经常不被允许这样做。我的几乎所有客户都不允许任何人进行此类更改。 因此,使用上述黑客技巧是我所知道的解决此问题的唯一可能替代方案。 - David Liebeherr

0

TransactionScope有多个构造函数。其中一些允许您在不进行配置更改的情况下更改超时时间。

using (var scope = new TransactionScope(new TransactionScopeOption(), TimeSpan.FromMinutes(3), TransactionScopeAsyncFlowOption.Enabled))
{
    // Long operation here

    scope.Complete();
}

0
在.NET Core(在NET6中测试过)中,您无需更改机器配置或使用反射。
您只需编写:
TransactionManager.MaximumTimeout = TimeSpan.FromMinutes(60);

一个小时的超时时间。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接