如何使两个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();只是因为我从一个使用它的方法开始。实际上,我将使用ScalarReader

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

当您对尚未完成的任务使用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语句的顺序相同。