2010-06-22 113 views
18

我喜歡在TPL的的Parallel.For和Parallel.ForEach擴展方法的簡單性。我想知道是否有辦法利用類似的東西,甚至是稍微更先進的任務。有沒有辦法在SQLDataReader中使用任務並行庫(TPL)?

下面是SqlDataReader中的典型使用,我想知道,如果有可能,如果是如何在第三方物流的東西替換下面的while循環。由於讀者無法提供固定次數的迭代,所以For擴展方法不可能處理我將收集的任務。我希望有人可能已經解決了這個問題,並用ADO.net解決了一些問題。

using (SqlConnection conn = new SqlConnection("myConnString")) 
using (SqlCommand comm = new SqlCommand("myQuery", conn)) 
{ 
    conn.Open(); 

    SqlDataReader reader = comm.ExecuteReader(); 

    if (reader.HasRows) 
    { 
     while (reader.Read()) 
     { 
      // Do something with Reader 
     } 
    } 
} 

回答

17

你幾乎沒有。總結你在一個函數貼有此標誌的代碼:

IEnumerable<IDataRecord> MyQuery() 

,然後用這個替換您的// Do something with Reader代碼:

yield return reader; 

現在你有一些在單線程工作。不幸的是,當你通過查詢讀取結果這是一個參考,每次回到相同對象,該對象只是變異本身對每個迭代。這意味着如果你試圖並行運行它,你會得到一些非常奇怪的結果,因爲平行讀取會改變不同線程中使用的對象。您需要使用代碼將記錄的副本發送到並行循環。

在這一點上,不過,我喜歡做的是跳過記錄的額外拷貝,直接進入到一個強類型的類。更重要的是,我喜歡用一個通用的方法來做到這一點:

IEnumerable<T> GetData<T>(Func<IDataRecord, T> factory, string sql, Action<SqlParameterCollection> addParameters) 
{ 
    using (var cn = new SqlConnection("My connection string")) 
    using (var cmd = new SqlCommand(sql, cn)) 
    { 
     addParameters(cmd.Parameters); 

     cn.Open(); 
     using (var rdr = cmd.ExecuteReader()) 
     { 
      while (rdr.Read()) 
      { 
       yield return factory(rdr); 
      } 
     } 
    } 
} 

假設你的工廠方法創建如預期的副本,此代碼應該是安全的Parallel.ForEach循環使用。調用該方法會是這個樣子(假設一個Employee類名爲靜態工廠方法「創建」):

var UnderPaid = GetData<Employee>(Employee.Create, 
     "SELECT * FROM Employee WHERE AnnualSalary <= @MinSalary", 
     p => { 
      p.Add("@MinSalary", SqlDbType.Int).Value = 50000; 
     }); 
Parallel.ForEach(UnderPaid, e => e.GiveRaise()); 

重要更新:
我不是這個代碼,我一樣自信曾經是。當另一個線程正在進行復制時,一個單獨的線程仍然可以改變讀者。我可以對此加以鎖定,但是我也擔心另一個線程可能會在原始文件自己調用Read()之後但在開始複製之前調用更新閱讀器。因此,這裏的關鍵部分由整個while循環組成......在這一點上,你又回到了單線程。我期望有一種方法可以修改此代碼,以便按照預期爲多線程場景工作,但需要更多的研究。

+0

我跟你說你說的大部分內容,你在工廠上讓我失去了一點點。當使用yeild return factor(rdr)時,Func 工廠不匹配調用我認爲你的意思是Func 。所以不知道你的意思是按照預期複製。你的意思是基本上從讀者那裏讀取並返回類似於裏德在回覆中所說的MyDataClass? – 2010-06-22 23:10:59

+0

也看起來像你的GetData調用是我們的命令你有工廠func之前的SQL字符串。無論我認爲我得到了它,您的Employee.Create是您的工廠,能夠爲讀者提供所需的工作。我會玩一會兒,看看它是如何發生的。 – 2010-06-22 23:25:02

+0

是的,我的意思是Func 。將解決這個問題和參數不匹配。 – 2010-06-22 23:43:04

21

您將無法直接替換while循環。 SqlDataReader不是 一個線程安全類,所以你不能直接從多個線程中使用它。

這就是說,你可能潛在進程您使用TPL讀取的數據。這裏有幾個選項。最簡單的做法可能是讓你自己的IEnumerable<T>實現在讀寫器上工作,並返回一個包含你的數據的類或結構體。然後,您可以使用PLINQ或Parallel.ForEach語句來並行處理數據:

public IEnumerable<MyDataClass> ReadData() 
{ 
    using (SqlConnection conn = new SqlConnection("myConnString")) 
    using (SqlCommand comm = new SqlCommand("myQuery", conn)) 
    { 
     conn.Open(); 

     SqlDataReader reader = comm.ExecuteReader(); 

     if (reader.HasRows) 
     { 
      while (reader.Read()) 
      { 
       yield return new MyDataClass(... data from reader ...); 
      } 
     } 
    } 
} 

一旦你的方法,你可以直接處理這一點,通過PLINQ或TPL:

Parallel.ForEach(this.ReadData(), data => 
{ 
    // Use the data here... 
}); 

或者:

this.ReadData().AsParallel().ForAll(data => 
{ 
    // Use the data here... 
}); 
相關問題