2014-10-10 16 views
14

Sorunum gerçek bir proje sorununa dayanıyor, ancak System.Threading.Tasks kitaplığını hiç kullanmadım ya da iş parçacıkları içeren herhangi bir ciddi programlama gerçekleştirmeme rağmen sorusu özel kitaplık hakkında bilgi eksik olabilir. ve uyumsuzluğun gerçekten programlama anlamında ne anlama geldiğine dair daha genel bir yanlış anlaşılma.Gerçekten eşzamansız iki SQL sorgusu nasıl yapılır

Benim gerçek dünya durumum bu - Bir kullanıcı hakkında veri almam gerekiyor. Benim şu anki senaryomda finansal veri var, yani Accounts, tüm Deposits ve tüm kullanıcı için Consignations'a ihtiyacım var. Benim durumumda bu, her bir özellik için kayıtların milyonu sorgulamak anlamına gelir ve her sorgu nispeten yavaştır, ancak Accounts'un getirilmesi, Deposits'un getirilmesinden birkaç kez daha yavaştır. Bu yüzden kullanmak için gidiyorum üç banka ürünleri için üç sınıf tanımladık ve belirli kullanıcının tüm banka ürünlerinin Verileri getirilecek istediğimde böyle bir şey yapmak:

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

Yani sorun başlar Burada tüm bu üç listeyi aynı anda almam gerekiyor, çünkü onları tüm kullanıcı verilerini görüntülediğim görünüme ileteceğim. böylece üç ürün için veri toplamak için toplam süreyi (Ben doğru burada terimini kullanıyorum varsa) şimdi olduğu yürütme senkron olduğu gibi Fakat geçerli:

Total_Time = Time_To_Get_Accounts + Time_To_Get_Deposits + Time_To_Get_Consignations 

Bu iyi değil her sorgu çünkü nispeten yavaş olduğu için toplam süre oldukça büyüktür, fakat aynı zamanda accounts sorgusu diğer iki sorgudan çok daha fazla zaman alır, böylece bugün kafamın içine girme fikri - "Bu sorguları eşzamanlı olarak çalıştırabilirsem" idi. Belki de bu konuyla ilgili en büyük yanlış anlama geliyor ama benim için bu fikre en yakın olanı asenkronize etmektir, bu yüzden belki de Total_Time en yavaş sorgunun zamanı olmayacak, ancak üç sorgunun toplamından çok daha hızlı olacaktır.

Kodum karmaşık olduğundan, oldukça iyi yapmaya çalıştığım şeyi yansıttığımı düşündüğüm basit bir kullanım durumu oluşturdum.

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

ve ikinci yöntem: Ben iki yöntemi var böyle diyoruz

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

Eğer ben ilk GetAccounts() arayıp yürütme yavaş görebileceğiniz gibi Amaca göre, bir sonraki yönteme devam etme şansı veriyorum. Ancak belli bir süre için sonuç alamıyorum ve daha sonra konsolda aynı anda basılmış olsun.

Yani sorun - nasıl bir sonraki yönteme gitmek için, ilk yöntemin bitmesini beklemeyecek şekilde. Genel olarak kod yapısı o kadar önemli değil, anlamaya çalıştığım şey, aynı anda yürütmek için her iki soruyu da yapmanın bir yolu varsa. Buradaki örnek, belki de istenilen sonucu alabileceğim noktaya kadar genişletilebilen araştırmamın sonucudur.

P.S Sadece kullanıyorum bir yöntemle başladım çünkü ExecuteScalarAsync(); kullanıyorum. Gerçekte Scalar ve Reader kullanacağım.

cevap

12

Henüz tamamlanmamış bir görev için Result özelliğini kullandığınızda, arama iş parçacığı, işlem tamamlanana kadar engellenir.Bu durumda, GetAccounts işleminin, GetDeposits aramasına başlamadan önce tamamlanması gerektiği anlamına gelir.

Bu yöntemin paralel olduğundan (eş zamanlı CPU yoğun parçaları dahil) emin olmak istiyorsanız, bu işi başka bir iş parçacığına yüklemeniz gerekir. Bunu yapmak için basit yolu Task.Run kullanmak olacaktır: sadece bu yöntem arama ve eşzamanlı o kullanarak tamamlamak için bekleyebilir async olamaz ve bu yüzden await kullanamaz

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

Main Çünkü Wait.

+0

. Orijinal fikrim, sonucu farklı bir zamanda elde etmekti. Açıklamanızdan belki de bunun gibi belki de toplam zaman en yavaş süreye eşittir, ancak ikinci yönteme eklediğimde (int i = 0; i <19000000; i ++) { string s = i.ToString(); } 'zaman iki katına çıktı. Bu garip. Eğer simultane bir şekilde yapıldıysa ve bu durumda sorguların milisaniye sürdüğü için, yöntemlerden sadece birinin yavaşlamasıyla aynı zamanı beklemekteydim. – Leron

+0

Kendimi netleştirdiğime emin değilim. Benim fikrim şu ki, 'GetAccounts()' 3 saniye ve 'GetDeposits()' saniyeden az alırsa sonuç almak için 3 saniye bekliyorum. Ama eğer 'GetDeposits()' i 3sn yerine tekrar 3 sn yerine çalıştırmaya zorlarsam, çünkü bu en yavaş zaman ve görev aynı zamanda 6 saniye sonra sonuca ulaşır. Bu yaklaşımı olduğu gibi uygularsam ne olur ne olur. Bir şey mi eksik? – Leron

+0

@Leron asenkron, paralel olarak aynı değildir. Yöntemleriniz eşzamansız bir parçaya ve senkronize bir parçaya (I/O ve CPU) sahiptir. Eşzamansız bölümleri eşzamanlı olarak yürütürken, cevaplarımda olduğu gibi kolayca yapabilirsiniz. Senkronize parçayı paralel olarak yürütmek için, 2 iş parçacığı 'Task.Run' ile kullanmanız gerekir. İstediğin bu mu? – i3arnon

2

Genellikle Task.WaitAll işlevini kullanmayı tercih ediyorum. Bu kod segmenti için ayarlamak için, sadece int dönmek için GetAccounts/GetDeposits imzaları değiştirdi (public static int GetAccounts())

Ben doğrulamaya dönüşü atama aynı dizisindeki Console.WriteLine yerleştirilen GetAccounts önce bir GetDeposits döner vardır, ancak

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

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

int accounts = results[0]; 
int deposits = results[1]; 
+0

Tam olarak aradığım şey. Ne yaptığınız hakkında daha fazla bilgi edinme konusunda bazı yönergeler sağlayabilir misiniz? – Leron

+0

'Task.WaitAll()' ın 'void' türünde dönüşü olduğunu unutmayın, eğer görevler bir şey döndürürse sonucu elde edemezsiniz. Bunu almak için 'Task.WhenAll()' beklemesini kullanmanız gerekir. – abatishchev

+0

Bu, eşzamansız değil. Paralel olarak yürütülen 2 senkronize işlem. – i3arnon

6

sonra taşımak için uyumsuz ve paralel iki görevleri gerçekleştirmek için bir yolu muhtemelen en gereksiz ve var olan AJAX, sayfa oluşturulduktan sonra getirilecek ve verileri bir mağazaya yerleştirecektir. Her AJAX getirme eşzamansızdır. Sunucuda eşzamanlı SQL sorguları oluşturmak istiyorsanız?

Kullanımı:

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

Kaynak: Ben bir şey alamadım düşünüyorum I3arnon @

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

@Leron: [İşte tam örnek] (https://dotnetfiddle.net/IsAJwV) – abatishchev

0

o ASP.NET bize geçtiyse: Bu İşte 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()); 



    }