もうすぐです。関数に投稿したコードを次の署名でラップします。
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ループ全体で構成されます...そしてこの時点で、再びシングルスレッドに戻ります。マルチスレッドシナリオで期待どおりに機能するようにこのコードを変更する方法があると思いますが、さらに調査する必要があります。