20

TPLのParallel.ForおよびParallel.ForEach拡張メソッドのシンプルさが気に入っています。似たようなものを利用する方法があるのか​​、それとも少し高度なタスクを利用する方法があるのだろうかと思っていました。

以下は、SqlDataReaderの一般的な使用法です。可能かどうか、可能であれば、以下のwhileループをTPL内の何かに置き換える方法を考えていました。リーダーは一定の反復回数を提供できないため、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
        }
    }
}
4

2 に答える 2

26

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...
});
于 2010-06-22T20:10:52.650 に答える
20

もうすぐです。関数に投稿したコードを次の署名でラップします。

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ループで安全に使用できるはずです。メソッドの呼び出しは次のようになります(「Create」という名前の静的ファクトリメソッドを持つ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ループ全体で構成されます...そしてこの時点で、再びシングルスレッドに戻ります。マルチスレッドシナリオで期待どおりに機能するようにこのコードを変更する方法があると思いますが、さらに調査する必要があります。

于 2010-06-22T20:50:53.737 に答える