c#Win Form - CLR无法从COM上下文转换

时间:2016-11-19 11:12:45

标签: c# .net sql-server multithreading c#-3.0

SQL版:SQL Server 2008 R2标准版
应用程序:.Net 3.5(Windows窗体)

这是我在运行代码后收到的错误

  

CLR无法从COM上下文0xe88270过渡到COM上下文0xe88328 60秒。拥有目标上下文/公寓的线程很可能是在非抽空等待或处理非常长时间运行的操作而不抽取Windows消息。这种情况通常会对性能产生负面影响,甚至可能导致应用程序变得无响应或内存使用量随时间不断累积。为了避免这个问题,所有单线程单元(STA)线程都应该使用抽取等待原语(例如CoWaitForMultipleHandles)并在长时间运行操作期间定期泵送消息。

以下代码会产生上述错误有时。最有可能在插入了24,000多条记录后,它会出错。

代码目标
编写代码是为了插入虚拟条目来测试我的应用程序

Random rnd = new Random();

string Data = "";

for (int i = 0; i < 2000000; i++)
{
      Data = "Insert Into Table1(Field1)values('" + rnd.Next(0, 200000000) + "');" + Environment.NewLine +
             "Insert Into Table1(Field1)values('" + rnd.Next(0, 200000000) + "');" + Environment.NewLine +
             "Insert Into Table1(Field1)values('" + rnd.Next(0, 200000000) + "');" + Environment.NewLine +
             "Insert Into Table1(Field1)values('" + rnd.Next(0, 200000000) + "');" + Environment.NewLine +
             "Insert Into Table1(Field1)values('" + rnd.Next(0, 200000000) + "');" + Environment.NewLine +
             "Insert Into Table1(Field1)values('" + rnd.Next(0, 200000000) + "');";
      ExecuteQuery(Data);//Error is displayed here
}

“ExecuteQuery”代码

SqlConnection Conn = new SqlConnection("Connection String");

if (Conn == null)
{
      Conn = new SqlConnection(Program.ConnString);
}
if (Conn.State == ConnectionState.Closed)
{
      Conn.Open();
}
else
{
      Conn.Close();
      Conn.Open();
}

SqlCommand cmd = new SqlCommand();
cmd.Connection = Conn;
cmd.CommandTimeout = 600000;
cmd.CommandType = CommandType.Text;
cmd.CommandText = strsql;
cmd.ExecuteNonQuery();
Conn.Close();

注意: - 我编写了多个在查询中完全相同的插入语句,因为它会减少no。 SQL必须处理的查询

问题
如何优化我的代码以防止错误发生?

1 个答案:

答案 0 :(得分:1)

  

“CLR无法从COM上下文转换到COM上下文...持续60秒。拥有目标上下文/公寓的线程很可能是在进行非抽空等待或处理非常长时间运行没有抽取Windows消息的操作。“

从您的问题中不完全清楚您的代码如何详细工作,因此我假设您的应用程序不是多线程的,即执行2×6百万数据库INSERT的循环正在应用程序的主要运行(UI)线程。这可能需要一段时间(> 60秒),因此您的UI将在此期间冻结(即使其无响应)。这是因为当您的(阻塞)循环仍在运行时,Windows窗体永远不会有机会运行并对用户输入作出反应。我敢打赌这就是导致你引用警告的原因。

改为使用参数化SQL命令。

您可以做的第一件事就是将SqlCommand变成参数化的private void InsertRandomNumbers(int[] randomNumbers) { const string commandText = "INSERT INTO dbo.Table1 (Field1) VALUES (@field1);" using (var connection = new SqlConnection(connectionString) using (var command = new SqlCommand(commandText, connection)) { var field1Parameter = new SqlDataParameter("@field1", SqlDbType.Int); command.Parameters.Add(field1Parameter); connection.Open(); foreach (int randomNumber in randomNumbers) { field1Parameter.Value = randomNumber; /* int rowsAffected = */ command.ExecuteNonQuery(); } connection.Close(); } } 。然后,您将发出具有相同SQL文本的命令;只有单独提供的参数会有所不同:

commandText

注意INSERT如何定义为常量。这是一个很好的迹象,表明SQL Server也会将它识别为始终相同的命令 - 使用参数化命令,实际参数值是单独提供的 - 而SQL Server只会编译和优化语句一次(并将编译后的语句放入其缓存中)所以它可以随后重复使用)而不是一遍又一遍地做同样的事情。仅此一项就可以节省大量时间。

长时间运行的操作应该是异步的,因此您的UI不会冻结。

您可以做的另一件事是将数据库代码转移到后台线程,这样您的应用程序的UI就不会冻结。

假设您的数据库doWorkButton循环当前由按钮for触发。因此Click循环位于按钮private void doWorkButton_Click(object sender, EventArgs e) { … for (i = 0; i < 2000000; i++) { Data = … ExecuteQuery(Data); // note: I leave it as an exercise to you to combine the // above suggestion (parameterized queries) with this one. } } 事件处理程序中:

ExecuteQuery

我们要做的第一件事就是以三种方式更改您的Task方法:

  1. 使其异步。它的返回类型为void而不是async,其声明会使用ExecuteNonQueryAsync关键字进行扩充。

  2. 将其重命名为INSERT以反映两件事:它现在将是异步的,并且它不会执行查询。我们实际上并不期望从数据库private async Task ExecuteNonQueryAsync(string commandText) { using (var connection = new SqlConnection(connectionString) using (var command = new SqlCommand(commandText, connection)) { connection.Open(); /* int rowsAffected = */ await command.ExecuteNonQueryAsync(); connection.Close(); } } 获得结果。

  3. 我们重写方法体以使用ADO.NET异步方法而不是同步方法。

  4. 这可能最终看起来像:

    private async void doWorkButton_Click(object sender, EventArgs e)
    {
        try
        {
            …
            for (i = 0; i < 2000000; i++) { … }
            {
                Data = …
                await ExecuteNonQueryAsync(Data);
            }
        }
        catch
        {
            … // do not let any exceptions escape this handler method
        }
    }
    

    就是这样。现在我们需要修改处理程序方法以使其同步:

    INSERT

    这应该关注UI冻结业务。

    使用SqlBulkCopy可以更有效地完成数百万个INSERT语句。

    SQL Server具有批量插入数据的绝佳选项。使用此功能通常可以获得更好的性能,可以使用自己批量的private async void doWorkButton_Click(object sender, EventArgs e) { // Prepare the data to be loaded into your database table. // Note, this could be done more efficiently. var dataTable = new DataTable(); { dataTable.Columns.Add("Field1", typeof(int)); var rnd = new Random(); for (int i = 0; i < 12000000; ++i) { dataTable.Rows.Add(rnd.Next(0, 2000000)); } } using (var connection = new SqlConnection(connectionString)) using (var bulkCopy = new SqlBulkCopy(connection)) { bulkCopy.DestinationTableName = "dbo.Table1"; try { // This will perform a bulk insert into the table // mentioned above, using the data passed in as a parameter. await bulkCopy.WriteToServerAsync(dataTable); } catch { Console.WriteLine(ex.Message); } } } 语句。

    SqlBulkCopy

    我现在无法测试,但我希望这能让你开始。如果您想进一步改进基于IDataReader的解决方案,我建议您创建SqlBulkCopy的自定义实现,创建包含随机数据的“行”,然后传递实例其中.include?而不是预先填充的数据表。这意味着您不必在内存中保留庞大的数据表,但一次只能保留一个数据行。