如何正确地进行异步/并行数据库调用

Fli*_*kut 20 c# asp.net multithreading asynchronous

我正在寻找处理多个数据库调用的正确方法,这些调用可能会同时运行.查询仅针对使用在我的ASP.NET MVC应用程序中以编程方式组装到DataTable中的数据进行插入或合并的存储过程.

当然,我已经看到了一些关于async和的信息await,而这似乎是我需要做的事情,但我对如何实现它并不清楚.一些信息表明,这些呼叫仍然是顺序的,并且仍然会等待另一个呼叫完成.这似乎毫无意义.

最后,我想要一个解决方案,允许我在完成最长程序所需的时间内运行所有查询.我希望所有查询都能返回受影响的记录数(就像现在一样).

这就是我现在要做的事情(这绝不是平行的):

// Variable for number of records affected
var recordedStatistics = new Dictionary<string, int>();

// Connect to the database and run the update procedure
using (var dbc = new SqlConnection(db.Database.Connection.ConnectionString))
{
    dbc.Open();

    // Merge One procedure
    using (SqlCommand cmd = new SqlCommand("MergeOneProcedure", dbc))
    {
        // 5 minute timeout on the query
        cmd.CommandTimeout = 300;
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@TVP", MergeOneDataTable);

        // Execute procedure and record the number of affected rows
        recordedStatistics.Add("mergeOne", cmd.ExecuteNonQuery());
    }

    // Merge Two procedure
    using (SqlCommand cmd = new SqlCommand("MergeTwoProcedure", dbc))
    {
        // 5 minute timeout on the query
        cmd.CommandTimeout = 300;
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@TVP", MergeTwoDataTable);

        // Execute procedure and record the number of affected rows
        recordedStatistics.Add("mergeTwo", cmd.ExecuteNonQuery());
    }

    // Merge Three procedure
    using (SqlCommand cmd = new SqlCommand("MergeThreeProcedure", dbc))
    {
        // 5 minute timeout on the query
        cmd.CommandTimeout = 300;
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@TVP", MergeThreeDataTable);

        // Execute procedure and record the number of affected rows
        recordedStatistics.Add("mergeThree", cmd.ExecuteNonQuery());
    }

    // Merge Four procedure
    using (SqlCommand cmd = new SqlCommand("MergeFourProcedure", dbc))
    {
        // 5 minute timeout on the query
        cmd.CommandTimeout = 300;
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@TVP", MergeFourDataTable);

        // Execute procedure and record the number of affected rows
        recordedStatistics.Add("mergeFour", cmd.ExecuteNonQuery());
    }

    // Merge Five procedure
    using (SqlCommand cmd = new SqlCommand("MergeFiveProcedure", dbc))
    {
        // 5 minute timeout on the query
        cmd.CommandTimeout = 300;
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@TVP", MergeFiveDataTable);

        // Execute procedure and record the number of affected rows
        recordedStatistics.Add("mergeFive", cmd.ExecuteNonQuery());
    }

    dbc.Close();
}

return recordedStatistics;
Run Code Online (Sandbox Code Playgroud)

所有这些代码都在组装DataTables数据的相同方法中.我async对此的有限理解会让我相信我需要将以前的代码提取到自己的方法中.然后我会调用该方法并await返回.但是,我甚至都不了解它的开始.

我之前从未做过任何异步/并行/多线程编码.这种情况让我觉得这是进入的最佳时机.也就是说,我想学习最好的方法,而不是忘记错误的方式.

Yac*_*sad 18

以下是如何执行此操作的示例:

这里我创建两个方法来包装两个操作,你需要为其他操作做同样的事情:

public async Task<int> MergeOneDataTableAsync()
{
    // Merge One procedure
    using (SqlCommand cmd = new SqlCommand("MergeOneProcedure", dbc))
    {
        // 5 minute timeout on the query
        cmd.CommandTimeout = 300;
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@TVP", MergeOneDataTable);

        return await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
    }
}


public async Task<int> MergeTwoDataTableAsync()
{
    // Merge Two procedure
    using (SqlCommand cmd = new SqlCommand("MergeTwoProcedure", dbc))
    {
        // 5 minute timeout on the query
        cmd.CommandTimeout = 300;
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@TVP", MergeTwoDataTable);

        return await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我正在使用该ExecuteNonQueryAsync方法来执行查询.

然后你原来的方法看起来像这样:

using (var dbc = new SqlConnection(db.Database.Connection.ConnectionString))
{
    dbc.Open();

    Task<int> task1 = MergeOneDataTableAsync();
    Task<int> task2 = MergeTwoDataTableAsync();

    Task.WaitAll(new Task[]{task1,task2}); //synchronously wait

    recordedStatistics.Add("mergeOne", task1.Result);
    recordedStatistics.Add("mergeTwo", task2.Result);
}
Run Code Online (Sandbox Code Playgroud)

请注意,我保持此方法同步.另一个选项(实际上是更好的选项)是将方法转换为异步方法,如下所示:

public async Task<Dictionary<string, int>> MyOriginalMethod()
{
    //...
    using (var dbc = new SqlConnection(db.Database.Connection.ConnectionString))
    {
        dbc.Open();

        Task<int> task1 = MergeOneDataTableAsync();
        Task<int> task2 = MergeTwoDataTableAsync();

        int[] results = await Task.WhenAll(new Task<int>[]{task1,task2});

        recordedStatistics.Add("mergeOne", results[0]);
        recordedStatistics.Add("mergeTwo", results[1]);
    }

    //...
    return recordedStatistics;
}
Run Code Online (Sandbox Code Playgroud)

但这意味着你必须异步调用它(异步一直).

  • 你实际上有一个死锁等待在`MergeOneDataTableAsync`和`MergeTwoDataTableAsync`创建的任务上调用`Task.WaitAll`,它们都使用`await`而不是`ConfigureAwait(false)`. (4认同)