如何使两个SQL查询真正异步
本文关键字:异步 查询 SQL 何使两 | 更新日期: 2023-09-27 18:26:08
我的问题是基于一个真实的项目问题,但我从未使用过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()
,我故意放慢执行速度,这样我就有机会继续执行下一个方法。然而,我在一段时间内没有得到任何结果,然后我会同时在控制台上打印所有结果。
所以问题是——如何制作,这样我就不会等到第一个方法完成,然后再去下一个方法。一般来说,代码结构并没有那么重要,我真正想弄清楚的是,是否有任何方法可以使两个查询同时执行。这里的样本是我研究的结果,也许可以扩展到我会得到想要的结果。
第S页我使用ExecuteScalarAsync();
只是因为我从一个使用它的方法开始。实际上,我将使用Scalar
和Reader
。
当您对尚未完成的任务使用Result
属性时,调用线程将阻塞,直到操作完成。这意味着在您的情况下,GetAccounts
操作需要在对GetDeposits
的调用开始之前完成。
如果您想确保这些方法是并行的(包括同步CPU密集型部分),您需要将这些工作卸载到另一个线程。最简单的方法是使用Task.Run
:
static async Task Main()
{
var accountTask = Task.Run(async () => Console.WriteLine(await GetAccounts()));
var depositsTask = Task.Run(async () => Console.WriteLine(await GetDeposits()));
await Task.WhenAll(accountTask, depositsTask);
}
以下是异步和并行执行两项任务的方法:
Task<int> accountTask = GetAccounts();
Task<int> 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放在分配返回的同一线程中,以验证一个GetDeposits是否在GetAccounts之前返回,但这是不必要的,可能最好将其移动到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());
}
如果是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);
}
}
}
如果进程需要很长时间,异步就很好。另一种选择是使用一个存储过程来返回所有三个记录集。
adp = New SqlDataAdapter(cmd)
dst = New DataSet
adp.Fill(dst)
在页面的代码后面,将它们引用为dst。表(0),dst。表(1)和dst。表(2)。这些表的顺序与存储过程中的select语句的顺序相同。