如何使两个SQL查询真正异步

时间:2014-10-10 16:28:07

标签: c# multithreading ado.net task-parallel-library async-await

我的问题是基于一个真正的项目问题,但我从未使用System.Threading.Tasks库或执行任何涉及线程的严肃编程,所以我的问题可能是缺乏关于特定库的知识和更普遍的误解异步在编程方面的确意味着什么。

所以我的真实案例就是这个 - 我需要获取有关用户的数据。在我目前的情况下,它是财务数据,因此我要说,对于特定用户,我需要所有Accounts,所有Deposits和所有Consignations。在我的情况下,这意味着查询每个属性的数百万条记录,并且每个查询本身相对较慢,但是获取Accounts比获取Deposits慢几倍。所以我为我要使用的三种银行产品定义了三个类,当我想获取某些用户的所有银行产品的数据时,我做了类似这样的事情:

List<Account> accounts = GetAccountsForClient(int clientId);
List<Deposit> deposits = GetDepositsForClient(int clientId);
List<Consignation> consignations = GetConsignationsForClient(int clientId);

所以问题从这里开始我需要同时获取所有这三个列表,因为我要将它们传递给我显示所有用户数据的视图。但正如现在一样,执行是同步的(如果我在这里正确使用这个术语),那么收集所有三种产品的数据的总时间是:

Total_Time = Time_To_Get_Accounts + Time_To_Get_Deposits + Time_To_Get_Consignations

这不好,因为每个查询都比较慢,所以总时间很长,而且accounts查询比其他两个查询花费的时间要多得多,所以今天进入我脑海的想法是 - “如果我可以同时执行此查询,该怎么办”。也许这是我对这个主题的最大误解,但对我而言,最接近这个想法是让它们异步,所以也许Total_Time不会是最慢查询的时间,但是会比它的总和快得多所有三个查询。

由于我的代码很复杂,我创建了一个简单的用例,我认为,这反映了我想要做的很好。我有两种方法:

public static async Task<int> GetAccounts()
{
    int total1 = 0;
    using (SqlConnection connection = new SqlConnection(connString))
    {
        string query1 = "SELECT COUNT(*) FROM [MyDb].[dbo].[Accounts]";
        SqlCommand command = new SqlCommand(query1, connection);
        connection.Open();
        for (int i = 0; i < 19000000; i++)
        {
            string s = i.ToString();
        }
        total1 = (int) await command.ExecuteScalarAsync();
        Console.WriteLine(total1.ToString());
    }
    return total1;
}

和第二种方法:

public static async Task<int> GetDeposits()
{
    int total2 = 0;
    using (SqlConnection connection = new SqlConnection(connString))
    {
        string query2 = "SELECT COUNT(*) FROM [MyDb].[dbo].[Deposits]";
        SqlCommand command = new SqlCommand(query2, connection);
        connection.Open();
        total2 = (int) await command.ExecuteScalarAsync();
        Console.WriteLine(total2.ToString());
    }
    return total2;
}

我称之为:

static void Main(string[] args)
{
    Console.WriteLine(GetAccounts().Result.ToString());

    Console.WriteLine(GetDeposits().Result.ToString());
}

正如你所看到的那样,我先调用GetAccounts(),然后我故意减慢执行速度,这样我就有可能继续执行下一个方法。但是我在一段时间内没有得到任何结果,然后我同时在控制台上打印出来。

所以问题 - 如何使我不等第一个方法完成,以便转到下一个方法。一般来说代码结构并不重要,我真正想要弄清楚的是,是否有任何方法可以使两个查询同时执行。这里的样本是我研究的结果,可能会扩展到我能得到预期结果的程度。

P.S 我正在使用ExecuteScalarAsync();,因为我开始使用一种正在使用它的方法。实际上,我将使用ScalarReader

5 个答案:

答案 0 :(得分:15)

当您对尚未完成的任务使用Result属性时,调用线程将阻塞,直到操作完成。这意味着在您的情况下,GetAccounts操作需要在调用GetDeposits之前完成。

如果要确保这些方法是并行的(包括同步CPU密集型部分),则需要将该工作卸载到另一个线程。最简单的方法是使用Task.Run

static void Main(string[] args)
{
    var accountTask = Task.Run(async () => Console.WriteLine(await GetAccounts()));
    var depositsTask = Task.Run(async () => Console.WriteLine(await GetDeposits()));

    Task.WhenAll(accountTask, depositsTask).Wait();
}

由于Main不能async,因此无法使用await,您只需调用此方法并同步等待它使用{{1}完成}}

答案 1 :(得分:6)

这是一种异步和并行执行两项任务的方法:

Task<int> accountTask = GetAccounts();
Task<int> depositsTask = GetDeposits();

int[] results = await Task.WhenAll(accountTask, depositsTask);

int accounts = results[0];
int deposits = results[1];

答案 2 :(得分:2)

我通常更喜欢使用Task.WaitAll。要设置此代码段,我更改了GetAccounts / GetDeposits签名只是为了返回int(public static int GetAccounts()

我将Console.WriteLine放在与分配返回相同的线程中,以验证GetAccounts之前返回的一个GetDeposits,但这是不必要的,最好在Task.WaitAll之后移动它

     private static void Main(string[] args) {

        int getAccountsTask = 0;
        int getDepositsTask = 0;
        List<Task> tasks = new List<Task>() {
            Task.Factory.StartNew(() => {
                getAccountsTask = GetAccounts();
                Console.WriteLine(getAccountsTask);
            }),
            Task.Factory.StartNew(() => {
                getDepositsTask = GetDeposits();
                Console.WriteLine(getDepositsTask);

            })

        };
        Task.WaitAll(tasks.ToArray());



    }

答案 3 :(得分:0)

如果它的ASP.NET使用AJAX在呈现页面后获取并将数据放入存储中。每个AJAX提取都是异步的。如果要在服务器上同时创建SQL查询?

用法:

 // Add some queries ie. ThreadedQuery.NamedQuery([Name], [SQL])
 var namedQueries= new ThreadedQuery.NamedQuery[]{ ... };

 System.Data.DataSet ds = ThreadedQuery.RunThreadedQuery(
 "Server=foo;Database=bar;Trusted_Connection=True;", 
 namedQueries).Result;


 string msg = string.Empty;
 foreach (System.Data.DataTable tt in ds.Tables)
 msg += string.Format("{0}: {1}\r\n", tt.TableName, tt.Rows.Count);

来源:

public class ThreadedQuery
{

    public class NamedQuery
    {
        public NamedQuery(string TableName, string SQL)
        {
            this.TableName = TableName;
            this.SQL = SQL;
        }
        public string TableName { get; set; }
        public string SQL { get; set; }
    }
    public static async System.Threading.Tasks.Task<System.Data.DataSet> RunThreadedQuery(string ConnectionString, params NamedQuery[] queries)
    {

        System.Data.DataSet dss = new System.Data.DataSet();
        List<System.Threading.Tasks.Task<System.Data.DataTable>> asyncQryList = new List<System.Threading.Tasks.Task<System.Data.DataTable>>();

        foreach (var qq in queries)
            asyncQryList.Add(fetchDataTable(qq, ConnectionString));

        foreach (var tsk in asyncQryList)
        {
            System.Data.DataTable tmp = await tsk.ConfigureAwait(false);
            dss.Tables.Add(tmp);
        }

        return dss;

    }

    private static async System.Threading.Tasks.Task<System.Data.DataTable> fetchDataTable(NamedQuery qry, string ConnectionString)
    {
        // Create a connection, open it and create a command on the connection
        try
        {

            System.Data.DataTable dt = new System.Data.DataTable(qry.TableName);
            using (SqlConnection connection = new SqlConnection(ConnectionString))
            {
                await connection.OpenAsync().ConfigureAwait(false);
                System.Diagnostics.Debug.WriteLine("Connection Opened ... " + qry.TableName);
                using (SqlCommand command = new SqlCommand(qry.SQL, connection))
                {
                    using (SqlDataReader reader = command.ExecuteReader())
                    {
                        System.Diagnostics.Debug.WriteLine("Query Executed ... " + qry.TableName);

                        dt.Load(reader);

                        System.Diagnostics.Debug.WriteLine(string.Format("Record Count '{0}' ... {1}", dt.Rows.Count, qry.TableName));

                        return dt;
                    }
                }
            }
        }
        catch(Exception ex)
        {

            System.Diagnostics.Debug.WriteLine("Exception Raised ... " + qry.TableName);
            System.Diagnostics.Debug.WriteLine(ex.Message);

            return new System.Data.DataTable(qry.TableName);
        }

    }
}

答案 4 :(得分:0)

如果该过程需要很长时间,那么异步非常有用。另一种选择是使用一个存储过程返回所有三个记录集。

Update TableB Set Confirmation = TableA.RowID From TableA
Where TableA.Vendor = TableB.Vendor
And TableA.Cost = TableB.Cost
And TableA.Date = TableB.Date

在页面后面的代码中,将它们引用为dst.Tables(0),dst.Tables(1)和dst.Tables(2)。这些表将与存储过程中的select语句相同。