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