【问题标题】:How to properly make asynchronous / parallel database calls如何正确进行异步/并行数据库调用
【发布时间】:2016-02-13 00:50:57
【问题描述】:

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

当然,我看到了一些关于asyncawait 的信息,这似乎是我需要做的,但我对如何实现它并没有清楚的了解。一些信息说调用仍然是连续的,并且一个仍然在等待另一个完成。这似乎毫无意义。

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

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

// 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;

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

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

【问题讨论】:

    标签: c# asp.net multithreading asynchronous


    【解决方案1】:

    下面是一个例子:

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

    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);
        }
    }
    

    请注意,我正在使用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);
    }
    

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

    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;
    }
    

    但这意味着您必须异步调用它 (async all the way)。

    【讨论】:

    • 看起来不错。事实上,它是一个 GUI 应用程序,但我希望它会锁定 GUI,直到查询完成。完成后,我会加载一个视图,其中包含每个查询中受影响记录的数量。我想要/需要这些信息,所以我要么照看调用页面,要么在我给它一分钟完成后回到它。我只是没有看到每个查询等待 5 分钟的意义,一个接一个,当我可以等待相同的 5 分钟让它们一次完成时。谢谢!
    • MergeOneDataTableAsyncMergeTwoDataTableAsync 创建的任务上调用Task.WaitAll 实际上会出现死锁,它们都使用await 而没有ConfigureAwait(false)
    • 您能详细说明一下死锁问题吗?也许也能找到一种避免它的方法?
    • @KirillShlenskiy,同步版本也是如此。这就是我警告 OP 的原因。
    • @FlipperBizkut,关于什么可能导致代码的初始同步版本出现死锁的更多信息:blog.stephencleary.com/2012/07/dont-block-on-async-code.html 和这里:blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115163.aspx
    猜你喜欢
    • 2015-08-28
    • 2016-08-12
    • 2018-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-03
    • 1970-01-01
    相关资源
    最近更新 更多