wake-up-neo.com

Wie werden zwei SQL-Abfragen wirklich asynchron gemacht?

Mein Problem basiert auf einem realen Projektproblem, aber ich habe noch nie die System.Threading.Tasks-Bibliothek verwendet oder ernsthafte Programmierung mit Threads durchgeführt. Daher ist meine Frage möglicherweise eine Mischung aus fehlendem Wissen über die spezifische Bibliothek und allgemeinerem Missverständnis darüber, was asynchron wirklich bedeutet der Programmierung.

Mein realer Fall ist also der: Ich muss Daten über einen Benutzer abrufen. In meinem aktuellen Szenario sind es Finanzdaten. Nehmen wir an, ich benötige alle Accounts, alle Deposits und alle Consignations für einen bestimmten Benutzer. In meinem Fall bedeutet das, Millionen von Datensätzen für jede Eigenschaft abzufragen, und jede Abfrage ist selbst relativ langsam. Das Abrufen der Accounts ist jedoch um ein Vielfaches langsamer als das Abrufen der Deposits. Ich habe also drei Klassen für die drei Bankprodukte definiert, die ich verwenden werde, und wenn ich die Daten für alle Bankprodukte bestimmter Benutzer abrufen möchte, mache ich Folgendes:

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

Das Problem beginnt also hier. Ich muss alle drei Listen gleichzeitig abrufen, da ich sie an die Ansicht übergeben werde, in der alle Benutzerdaten angezeigt werden. Aber wie es jetzt ist, ist die Ausführung synchron (wenn ich den Begriff hier richtig verwende), also beträgt die Gesamtzeit zum Sammeln der Daten für alle drei Produkte:

Total_Time = Time_To_Get_Accounts + Time_To_Get_Deposits + Time_To_Get_Consignations

Das ist nicht gut, da jede Abfrage relativ langsam ist, also die Gesamtzeit ziemlich groß ist. Die accounts-Abfrage dauert jedoch viel mehr Zeit als die beiden anderen Abfragen. Die Idee, die mir heute in den Kopf kommt, war - "Was wäre, wenn ich könnte." Diese Abfragen gleichzeitig ausführen ". Vielleicht kommt hier mein größtes Missverständnis über das Thema, aber für mich ist diese Idee am nächsten, sie asynchron zu machen, sodass Total_Time vielleicht nicht der Zeitpunkt der langsamsten Abfrage ist, aber dennoch viel schneller als die Summe aller drei Abfragen.

Da mein Code kompliziert ist, habe ich einen einfachen Anwendungsfall erstellt, der meiner Meinung nach gut widerspiegelt. Ich habe zwei Methoden:

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

und die zweite Methode:

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

was ich so anrufe: 

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

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

Wie Sie sehen, rufe ich zuerst GetAccounts() auf und verlangsame die Ausführung absichtlich, so dass ich die Möglichkeit habe, mit der nächsten Methode fortzufahren. Allerdings bekomme ich für eine bestimmte Zeit kein Ergebnis und dann alle gleichzeitig auf die Konsole gedruckt.

Also das Problem - wie mache ich das, damit ich nicht warte, bis die erste Methode fertig ist, um zur nächsten Methode zu gelangen. Im Allgemeinen ist die Codestruktur nicht so wichtig. Ich möchte herausfinden, ob es eine Möglichkeit gibt, beide Abfragen gleichzeitig auszuführen. Die Probe hier ist das Ergebnis meiner Recherche, die vielleicht bis zu dem Punkt erweitert werden kann, an dem ich das gewünschte Ergebnis bekomme.

P.S Ich benutze ExecuteScalarAsync();, nur weil ich mit einer Methode angefangen habe, die sie verwendet hat. In Wirklichkeit werde ich Scalar und Reader verwenden.

16
Leron

Wenn Sie die Result-Eigenschaft für eine Aufgabe verwenden, die noch nicht abgeschlossen ist, wird der aufrufende Thread blockiert, bis der Vorgang abgeschlossen ist. Das bedeutet in Ihrem Fall, dass die GetAccounts-Operation abgeschlossen sein muss, bevor der Aufruf von GetDeposits beginnt.

Wenn Sie sicherstellen möchten, dass diese Methoden parallel sind (einschließlich der synchronen, CPU-intensiven Teile), müssen Sie diese Arbeit in einen anderen Thread verlagern. Der einfachste Weg wäre, Task.Run zu verwenden:

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

Da Main nicht async sein kann und await nicht verwenden kann, können Sie diese Methode einfach aufrufen und synchron warten, bis sie mit Wait abgeschlossen ist.

14
i3arnon

So können Sie zwei Aufgaben asynchron und parallel ausführen:

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

Im Allgemeinen bevorzuge ich Task.WaitAll. Um dieses Codesegment einzurichten, habe ich die GetAccounts/GetDeposits-Signaturen geändert, um nur int (public static int GetAccounts()) zurückzugeben.

Ich habe die Console.WriteLine in den gleichen Thread gestellt, in dem die Rückgabe zugewiesen wurde, um zu überprüfen, ob eine GetDeposits-Rückgabe vor GetAccounts erfolgt. Dies ist jedoch nicht erforderlich und wird wahrscheinlich am besten nach Task.WaitAll Verschoben.

     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

Async ist großartig, wenn der Vorgang lange dauert. Eine andere Option wäre, eine gespeicherte Prozedur zu haben, die alle drei Datensatzgruppen zurückgibt.

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

Verweisen Sie in dem Code hinter der Seite auf sie als Dst.Tables (0), Dst.Tables (1) und Dst.Tables (2). Die Tabellen befinden sich in derselben Reihenfolge wie die SELECT-Anweisungen in der gespeicherten Prozedur.

0
Joe Sweeney

Wenn es sich um ASP.Net handelt Verwenden Sie AJAX, um nach dem Rendern der Seite abzurufen und die Daten in einem Geschäft abzulegen. Jeder Abruf von AJAX ist asynchron. Wenn Sie simultane SQL-Abfragen auf dem Server erstellen möchten?

Verwendungszweck:

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

Quelle:

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