web-dev-qa-db-ja.com

2つのSQLクエリを本当に非同期にする方法

私の問題は実際のプロジェクトの問題に基づいていますが、_System.Threading.Tasks_ライブラリを使用したり、スレッドを含む深刻なプログラミングを実行したことがないため、特定のライブラリに関する知識の欠如と非同期の一般的な誤解が混在する可能性があります本当にプログラミングという意味です。

私の現実のケースはこれです-ユーザーに関するデータを取得する必要があります。私の現在のシナリオでは、財務データなので、特定のユーザーのすべてのAccounts、すべてのDeposits、およびすべてのConsignationsが必要だとしましょう。私の場合、これは各プロパティに対して何百万ものレコードをクエリすることを意味し、各クエリ自体は比較的低速ですが、AccountsをフェッチすることはDepositsをフェッチするよりも数倍遅くなります。使用する3つの銀行商品に3つのクラスを定義し、特定のユーザーのすべての銀行商品のデータを取得する場合、次のようにします。

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

問題はここから始まります。これら3つのリストをすべて同時に取得する必要があります。すべてのユーザーデータを表示するビューにそれらを渡すためです。しかし、現時点では実行は同期的であるため(ここで用語を正しく使用している場合)、3つの製品すべてのデータを収集するための合計時間は次のとおりです。

_Total_Time = Time_To_Get_Accounts + Time_To_Get_Deposits + Time_To_Get_Consignations
_

各クエリは比較的遅く、合計時間がかなり長いため、これは良くありませんが、accountsクエリは他の2つのクエリよりもはるかに時間がかかるため、今日頭に浮かぶアイデアは- 「このクエリを同時に実行できたらどうでしょう」。たぶんここで私の最大の誤解が来るかもしれませんが、私にとってこの考えに最も近いのは非同期にすることです。そうすれば_Total_Time_は最も遅いクエリの時間ではなく、合計よりはるかに速くなります3つのクエリすべて。

私のコードは複雑なので、私が考えている簡単なユースケースを作成しました。私には2つの方法があります:

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

そして2番目の方法:

_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()を呼び出して、意図的に実行を遅くするので、実行が次のメソッドに進むチャンスを与えます。しかし、一定期間結果が得られず、コンソールにすべてが同時に印刷されます。

だから問題-次の方法に進むために、最初の方法が終わるのを待たないようにする方法。一般に、コード構造はそれほど重要ではありませんが、両方のクエリを同時に実行する方法があるかどうかを本当に理解したいと思います。ここのサンプルは私の研究の結果であり、望みの結果が得られるまで拡張できる可能性があります。

追伸:私はExecuteScalarAsync();を使用していますが、それを使用していたメソッドで始めたからです。実際には、ScalarReaderを使用します。

18
Leron

以下に、2つのタスクを非同期で並行して実行する方法を示します。

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

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

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

私は通常Task.WaitAllを使用することを好みます。このコードセグメントのセットアップのために、GetAccounts/GetDepositsシグネチャを変更して、int(public static int GetAccounts())を返すようにしました。

Console.WriteLineは、GetAccountsの前に1つの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
AWinkle

プロセスに時間がかかる場合は、非同期が最適です。別のオプションは、3つのレコードセットすべてを返す1つのストアドプロシージャを持つことです。

        adp = New SqlDataAdapter(cmd)
        dst = New DataSet
        adp.Fill(dst)

ページの背後にあるコードで、dst.Tables(0)、dst.Tables(1)、およびdst.Tables(2)としてそれらを参照します。テーブルは、ストアドプロシージャのselectステートメントと同じ順序になります。

0
Joe Sweeney

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

    }
}
0
Juls