Parallel.Invoke(),TransactionScope()和SqlBulkCopy

时间:2014-06-06 14:33:26

标签: c# multithreading task-parallel-library transactionscope sqlbulkcopy

我在Parallel.Invoke()内部有多个需要在事务内部运行的方法。这些方法都调用SqlBulkCopy的实例。用例是" all-or-none",所以如果一个方法失败,则不会提交任何内容。当我在父事务上调用Complete()方法时,我得到TransactionAbortedException ({"Transaction Timeout"})

这是父交易:

using (var ts = new TransactionScope())
     {
       var saveClone = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
       var saveErrorsClone = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
        var saveADClone = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
        var saveEnrollmentsClone = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
        Parallel.Invoke(_options, () =>
                    {
                        Save(data, saveClone);
                    },
                    () =>
                    {
                        SaveErrors(saveErrorsClone);
                    },
                    () =>
                    {
                        SaveEnrollments(data, saveEnrollmentsClone);
                    });
        ts.Complete();  
      }//***** GET THE EXCEPTION HERE *****

这是一个使用SqlBulkCopy的依赖交易(它们都是相同的结构)。我传递了父母,并将其分配给孩子TransactionScope

private void Save(IDictionary<string, string> data, Transaction transaction)
        {
        var dTs = (DependentTransaction)transaction;

        if (transaction.TransactionInformation.Status != TransactionStatus.Aborted)
            {
               using (var ts = new TransactionScope(dTs))
                   {
                     _walmartData.Save(data);
                     Debug.WriteLine("Completed Processing XML - {0}", _stopWatch.Elapsed);
                      ts.Complete();
                    }
             }
        else
             {
                Debug.WriteLine("Save Not Executed - Transaction Aborted - {0}", _stopWatch.Elapsed);    
                dTs.Complete();
             }
        dTs.Complete();
}

编辑(添加了我的SqlBulkCopy方法...... 通知事件参数

private void SqlBulkCopy(DataTable dt, SqlBulkCopyColumnMappingCollection mappings)
        {
            try
            {
                using (var sbc = new SqlBulkCopy(_conn, SqlBulkCopyOptions.TableLock, null))
                {
                    sbc.BatchSize = 100;
                    sbc.BulkCopyTimeout = 0;
                    sbc.DestinationTableName = dt.TableName;

                    foreach (SqlBulkCopyColumnMapping mapping in mappings)
                    {
                        sbc.ColumnMappings.Add(mapping);
                    }

                    sbc.WriteToServer(dt);
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

除了修正错误外,我还可以选择替代方案。感谢。

4 个答案:

答案 0 :(得分:2)

您可以选择DependentCloneOption.BlockCommitUntilComplete创建一种死锁形式。

Parallel.Invoke阻塞调用线程,直到所有处理完成。尝试由Parallel.Invoke完成的作业在等待父事务完成时都会阻塞(由于DependentCloneOption)。所以这两个人正在等待......死锁。父事务最终会超时并从阻塞中释放依赖事务,这会阻止您的调用线程。

您可以使用DependentCloneOption.RollbackIfNotComplete吗?

答案 1 :(得分:0)

http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.complete.aspx表示TransactionScope.Complete只提交它包含的事务,如果它是创建事务的事务。由于您是从现有事务创建范围,我相信您需要在调用范围完成之前提交事务。

来自MSDN:

  

资源管理器之间的实际提交工作发生在   如果TransactionScope对象创建了,则结束使用语句   交易。如果它没有创建事务,则发生提交   每当Committable由CommittableTransaction的所有者调用时   宾语。此时,事务管理器调用该资源   管理员并通知他们提交或回滚,基于   是否在TransactionScope对象上调用此方法

答案 2 :(得分:0)

在经历了很多痛苦,研究和缺乏有效答案后,我不得不相信我在问题中描述的堆栈是不可能的。我相信,痛点在于TransactionScope和SqlBulkCopy之间。我把这个答案放在这里是为了未来观众的利益。如果有人能证明可以做到,我很乐意将其删除作为答案。

答案 3 :(得分:0)

我相信您创建_conn - 实例的方式非常重要,如果您创建并在TransactionScope - 实例中打开它,则任何SqlBulkCopy相关问题都应该得到解决。

查看Can I use SqlBulkCopy inside TransactionIs it possible to use System.Transactions.TransactionScope with SqlBulkCopy?,看看它是否对您有帮助。

void MyMainMethod()
{
 using (var ts = new TransactionScope())
 {
  Parallell.InvokeOrWhatNotOrWhatEver(() => DoStuff());
 }
}

void DoStuff() 
{
 using (var sqlCon = new SqlConnection(conStr))
 {
  sqlCon.Open(); // ensure to open it before SqlBulkCopy can open it in another transactionscope.
  using (var bulk = new SqlBulkCopy(sqlCon))
  {
    // Do you stuff
    bulk.WriteToServer...
  }      

  ts.Complete(); // finish the transaction, ie commit
 }
}

简而言之:

  1. 创建交易范围
  2. 创建sql-connection并在事务范围
  3. 下打开它
  4. 创建并使用SqlBulkCopy - 具有上面创建的conncection的实例
  5. 致电transaction.Complete()
  6. 处理一切: - )