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

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

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

 List accounts = GetAccountsForClient(int clientId); List deposits = GetDepositsForClient(int clientId); List 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 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 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()并且我故意减慢执行速度,因此我有机会执行继续下一个方法。 但是我在一段时间内没有得到任何结果,然后我同时在控制台上打印。

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

PS我正在使用ExecuteScalarAsync(); 只是因为我开始使用一种正在使用它的方法。 实际上我会使用ScalarReader

在尚未完成的任务上使用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你可以简单地调用这个方法并同步等待它使用Wait完成。

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

 Task accountTask = GetAccounts(); Task depositsTask = GetDeposits(); int[] results = await Task.WhenAll(accountTask, depositsTask); int accounts = results[0]; int deposits = results[1]; 

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

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

  private static void Main(string[] args) { int getAccountsTask = 0; int getDepositsTask = 0; List tasks = new List() { Task.Factory.StartNew(() => { getAccountsTask = GetAccounts(); Console.WriteLine(getAccountsTask); }), Task.Factory.StartNew(() => { getDepositsTask = GetDeposits(); Console.WriteLine(getDepositsTask); }) }; Task.WaitAll(tasks.ToArray()); } 

如果是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 RunThreadedQuery(string ConnectionString, params NamedQuery[] queries) { System.Data.DataSet dss = new System.Data.DataSet(); List> asyncQryList = new List>(); 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 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); } } }