C# 使用同一SqlConnection对SqlCommand.BeginExecutenQuery的多个并发调用

C# 使用同一SqlConnection对SqlCommand.BeginExecutenQuery的多个并发调用,c#,sql-server,multithreading,ado.net,C#,Sql Server,Multithreading,Ado.net,我有一些工作的C#代码,它使用SqlConnection创建临时表(例如#Foo),调用存储的进程来填充这些临时表并将结果返回给C#客户端,使用C#对这些结果执行复杂的计算,并使用计算结果更新先前创建的一个临时表 由于在整个过程中使用临时表,我们必须只有一个SqlConnection 在用计算结果更新temp表时,我发现了一个性能瓶颈。这段代码已经在批处理更新,以防止C#客户端内存不足。每批计算数据都通过SqlCommand.ExecuteNonQuery发送到存储过程,存储过程反过来更新临时表

我有一些工作的C#代码,它使用SqlConnection创建临时表(例如#Foo),调用存储的进程来填充这些临时表并将结果返回给C#客户端,使用C#对这些结果执行复杂的计算,并使用计算结果更新先前创建的一个临时表

由于在整个过程中使用临时表,我们必须只有一个SqlConnection

在用计算结果更新temp表时,我发现了一个性能瓶颈。这段代码已经在批处理更新,以防止C#客户端内存不足。每批计算数据都通过SqlCommand.ExecuteNonQuery发送到存储过程,存储过程反过来更新临时表。代码大部分时间都在调用ExecuteOnQuery

因此,我将其更改为BeginExecutenQuery,以及等待线程并调用EndExecutenQuery的代码。这将性能提高约三分之一,但我担心使用同一个SqlConnection会有多个对SqlCommand.BeginExecutenQuery的并发调用

这样行吗,还是会遇到线程问题

对不起,解释得太长了

MSDN文档说明:

BeginExecutenQuery方法立即返回,但在代码执行相应的EndExecutenQuery方法调用之前,它不得执行对同一SqlCommand对象启动同步或异步执行的任何其他调用

这似乎意味着不同的SqlCommand对象可以在第一个SqlCommand完成之前调用BeginExecutenQuery

下面是一些说明问题的代码:

    private class SqlCommandData
    {
        public SqlCommand Command { get; set; }
        public IAsyncResult AsyncResult { get; set; }
    }

    public static void TestMultipleConcurrentBeginExecuteNonQueryCalls(string baseConnectionString)
    {
        var connectionStringBuilder = new SqlConnectionStringBuilder(baseConnectionString)
                                          {
                                              MultipleActiveResultSets = true,
                                              AsynchronousProcessing = true
                                          };
        using (var connection = new SqlConnection(connectionStringBuilder.ConnectionString))
        {
            connection.Open();

            // ELIDED - code that uses connection to do various Sql work

            SqlDataReader dataReader = null;
                // in real code, this would be initialized from calls to SqlCommand.ExecuteReader, using same connection

            var commandDatas = new List<SqlCommandData>();
            var count = 0;
            const int maxCountPerJob = 10000;
            while (dataReader.Read())
            {
                count++;
                // ELIDED - do some calculations on data, too complex to do in SQL stored proc
                if (count >= maxCountPerJob)
                {
                    count = 0;
                    var commandData = new SqlCommandData
                                          {
                                              Command = new SqlCommand {Connection = connection}
                                          };
                    // ELIDED - other initialization of command - used to send the results of calculation back to DB
                    commandData.AsyncResult = commandData.Command.BeginExecuteNonQuery();
                    commandDatas.Add(commandData);
                }
            }
            dataReader.Close();

            WaitHandle.WaitAll(commandDatas.Select(c => c.AsyncResult.AsyncWaitHandle).ToArray());
            foreach (var commandData in commandDatas)
            {
                commandData.Command.EndExecuteNonQuery(commandData.AsyncResult);
                commandData.Command.Dispose();
            }

            // ELIDED - more code using same SqlConnection to do final work

            connection.Close();
        }
    }
私有类SqlCommandData
{
公共SqlCommand命令{get;set;}
公共IAsyncResult AsyncResult{get;set;}
}
公共静态void TestMultipleConcurrentBeginExecutenQueryCalls(字符串baseConnectionString)
{
var connectionStringBuilder=new-SqlConnectionStringBuilder(baseConnectionString)
{
MultipleActiveResultSets=true,
AsynchronousProcessing=true
};
使用(var connection=newsqlconnection(connectionStringBuilder.ConnectionString))
{
connection.Open();
//ELIDED—使用连接执行各种Sql工作的代码
SqlDataReader=null;
//在实际代码中,这将使用相同的连接从对SqlCommand.ExecuteReader的调用中初始化
var commandDatas=新列表();
var计数=0;
const int maxCountPerJob=10000;
while(dataReader.Read())
{
计数++;
//省略-对数据执行一些计算,这些计算太复杂,无法在SQL存储过程中执行
如果(计数>=maxCountPerJob)
{
计数=0;
var commandData=新的SqlCommandData
{
Command=newsqlcommand{Connection=Connection}
};
//ELIDED-其他命令初始化-用于将计算结果发送回DB
commandData.AsyncResult=commandData.Command.BeginExecutenQuery();
Add(commandData);
}
}
dataReader.Close();
WaitHandle.WaitAll(commandDatas.Select(c=>c.AsyncResult.AsyncWaitHandle.ToArray());
foreach(commandDatas中的var commandData)
{
commandData.Command.EndExecuteNonQuery(commandData.AsyncResult);
commandData.Command.Dispose();
}
//省略-更多代码使用相同的SqlConnection完成最终工作
connection.Close();
}
}

是的,问得很好

也许您可以使用SQL Server 2005中引入的一个功能,称为MARS:

MARS允许重复使用相同的连接进行读写,但它有一些限制,坦白说,我不知道有谁会使用它

从我看来,也许可以从不同的角度来看待你的问题。也许,您可以创建一组包含额外列JobId的永久表,而不是使用临时表并在整个过程中一直关注它们,这最终必须是同步的。这样,您就不受单线程的约束。您可以有一个表来保存作业的历史记录。向该表插入一行后,检索scope_identity()并将其添加到算法的所有元素中。这些表一次可以保存多个结果副本,任何读取或更新数据的查询都将使用JobId作为set标识符。如果您对表进行了正确的索引,您将拥有非常平滑的设计,这将比您现在尝试实现的解决方案具有更大的可伸缩性

问候


Piotr

如果运行数据修改语句对您有帮助,那么这就是一个问题。MARS是多个活动结果集的首字母缩略词-结果集是
SELECT
FETCH
语句的结果,在.NET中,它通常意味着您可以在同一连接上打开多个DataReader。但任何数据修改操作都被视为原子操作,必须先完成,然后才能执行其他操作(或继续从结果集中检索数据)。因此,我认为您的异步命令处于初始状态,并且仍然按顺序执行

如果主连接创建全局临时表
##TempName
而不是
#Temp
,则可以使用多个连接。地球仪
                var commandData = new SqlCommandData
                                      {
                                          Command = new SqlCommand {Connection = connection}
                                      };
                connection.Open();
                // ELIDED - other initialization of command - used to send the results of calculation back to DB
                commandData.AsyncResult = commandData.Command.BeginExecuteNonQuery();