事务范围性能问题

3
我在使用TransactionScope类处理服务器端代码(WCF)时遇到了性能问题。
我的代码接收客户端请求,创建TransactionScope并执行短操作(通常不超过100毫秒)。
请参考以下附加的代码,模拟我的服务器端代码。当有100个或更多并发用户时,创建新的TransactionScope(请参见GetTransaction()方法)需要超过1秒钟时间!
并且当并发用户数达到200时,会抛出TransactionAborted异常。
你有什么想法吗?
class Program
     {
         private static ConcurrentQueue<double> m_Queue = new ConcurrentQueue<double>();

     static void Main(string[] args)
     {
         Console.WriteLine("Press any key to start ...");
         Console.ReadKey();

         for (int i = 0; i < 100; i++)
         {
             Thread t = new Thread(new ThreadStart(Method));
             t.IsBackground = true;
             t.Start();
         }

         Thread.Sleep(2000);
         Console.WriteLine("Max {0}, Min {1}, Avg {2}, Total {3}", m_Queue.Max(), m_Queue.Min(), m_Queue.Average(), m_Queue.Count);
         Console.ReadKey();
     }


     private static void Method() 
     {
         using (TransactionScope scope = GetTransaction())
         {
             Thread.Sleep(100);
             scope.Complete();
         }
     }

     public static TransactionScope GetTransaction()
     {
         var start = DateTime.Now;

         TransactionOptions options = new TransactionOptions();
         options.IsolationLevel = IsolationLevel.ReadCommitted;
         var t = new TransactionScope(TransactionScopeOption.Required, options);

         // Log creation time
         m_Queue.Enqueue((DateTime.Now.Subtract(start)).TotalMilliseconds);

         return t;
     }

 }
2个回答

0

初步回答:

如果我使用一个简单的List而不是ConcurrentQueue进行测试(我没有安装.NET 4.5),你的代码在我的环境中可以正常工作。

由于您同时启动了200个线程,因此您的这行代码:

Console.WriteLine("Max {0}, Min {1}, Avg {2}, Total {3}", m_Queue.Max(), m_Queue.Min(), m_Queue.Average(), m_Queue.Count);

基本上开始迭代队列,这会阻塞队列,导致仍在启动的线程在尝试将项目添加到队列时冻结,并进而导致交易超时。

您能否尝试不使用上述控制台写入行?同时,尝试使用普通列表。

一种解决方案是在尝试执行最大/最小/平均/总函数之前等待线程完成。

需要注意的一件事是,我在测试中得到的总数并不是完整的100或200。它只是略低于这个数字,这意味着仍有线程正在启动。

更新:

如果有帮助的话,我遇到的任何问题都可以通过将enqueue调用移到方法中来解决。在您的情况下,这样做是否有效?

public static TransactionScope GetTransaction() {
    var start = DateTime.Now;

    // Log creation time
    m_Queue.Enqueue((DateTime.Now.Subtract(start)).TotalMilliseconds);

    TransactionOptions options = new TransactionOptions();
    options.IsolationLevel = IsolationLevel.ReadCommitted;
    var t = new TransactionScope(TransactionScopeOption.Required, options);

    return t;
}

无法使用列表,因为队列正在多个线程上被访问。 - casperOne
问题(错误地)指示为transactionscope,但我认为问题在于并发队列,因为它会停止线程的执行,从而导致transactionscope出现问题。解决问题,而不是副作用。 - Maarten
你并没有真正解决问题,而是创造了一个新的问题。当存在争用时,并发队列会暂停一些线程的执行。这也会带来开销,因为它必须检查是否存在争用。去掉这个检查将导致数据损坏。你只是把一个问题换成了另一个问题。在这种情况下,同时访问是必须的 - casperOne
好的,但不幸的是,目前强制并发访问的方式在使用事务范围时会导致异常。如果OP提到了对并发访问的要求,那么我们也会知道。这在原始问题中没有提到。事实仍然存在,如果在事务进行时阻止线程的执行,可能会导致问题。 - Maarten
我认为100个访问队列的线程会使并发需求变得隐式... - casperOne
我发现当你将enqueue行移到方法中时,异常就会消失(请参见我的更新答案)。在你的情况下,这样做是否可行? - Maarten

0

移除你的“enqueue”代码行,看看问题是否仍然存在。

另外,我对在事务范围内执行的“短操作”很好奇。它们可能会将事务协调器从LTM升级到MSDTC,这可能解释了更多并发访问时的缓慢。


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