簡體   English   中英

有沒有辦法將任務並行庫(TPL)與SQLDataReader一起使用?

[英]Is there a way to use the Task Parallel Library(TPL) with SQLDataReader?

我喜歡TPL中Parallel.For和Parallel.ForEach擴展方法的簡單性。 我想知道是否有辦法利用類似的東西,甚至是稍微高級的任務。

下面是SqlDataReader的典型用法,我想知道是否可能,如果是這樣,如何用TPL中的東西替換下面的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
        }
    }
}

你將很難直接替換while循環。 SqlDataReader不是線程安全類,因此您無法直接從多個線程使用它。

話雖這么說,您可以使用TPL 處理您讀取的數據。 這里有幾個選項。 最簡單的方法是創建適用於閱讀器的IEnumerable<T>實現,並返回包含數據的類或結構。 然后,您可以使用PLINQ或Parallel.ForEach語句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...
});

你快到了。 使用此簽名包裹您在函數中發布的代碼:

IEnumerable<IDataRecord> MyQuery()

然后// Do something with Reader代碼替換// Do something with Reader代碼// 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類具有名為“Create”的靜態工廠方法):

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循環......此時,您又回到了單線程。 我希望有一種方法可以修改此代碼,以便在多線程方案中按預期工作,但需要更多學習。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM