私の目標は単純です。非同期I/O呼び出しを実行したい(非同期待機を使用)-しかし:
OK。
現在ここに私のコードは、それがdbから読み取り、各行を_Func<>
_に投影することです
_public IEnumerable < T > GetSomeData < T > (string sql, Func < IDataRecord, T > projector)
{
using(SqlConnection _conn = new SqlConnection(@"Data Source=..."))
{
using(SqlCommand _cmd = new SqlCommand(sql, _conn))
{
_conn.Open();
_cmd.CommandTimeout = 100000;
using(IDataReader rdr = _cmd.ExecuteReader())
{
while (rdr.Read()) yield return projector(rdr);
}
}
}
}
_
それで、プロジェクターとは何ですか?
各クラスには、record
(IDataRecord
)を取得してエンティティを作成する関数があります。
例:
_public class MyClass
{
public static MyClass MyClassFactory(IDataRecord record)
{
return new MyClass
{
Name = record["Name"].ToString(),
Datee = DateTime.Parse(record["Datee"].ToString()),
val = decimal.Parse(record["val"].ToString())
};
}
public string Name { get; set; }
public DateTime Datee { get; set; }
public decimal val { get; set; }
}
_
したがって、ここでは、MyClassFactory
はFunc
になります
それで、私は現在それをどのように実行していますか?
_ var sql = @"SELECT TOP 1000 [NAME],[datee] ,[val] FROM [WebERP].[dbo].[t]";
var a = GetSomeData < MyClass > (sql, MyClass.MyClassFactory).Where(...); //notice the Func
_
大丈夫です。
問題は今始まる:
メソッドにasync
を追加すると、エラーが発生します:(はい、Ienumerableが同期インターフェイスであることがわかっているため、問題が発生します)
public async Task<IEnumerable < T >> GetSomeData < T > (string sql, Func < IDataRecord, T > projector)
'System.Threading.Tasks.Task>'はイテレータインターフェイスタイプではないため、イテレータブロックにすることはできません
どちらが[〜#〜]何を行うか[〜#〜]コンパイルします。
質問
私のコードを完全に非同期をサポートするように変換するにはどうすればよいですかIO呼び出し?
(条件:DataFlow依存関係なし、プロジェクター関数を引数として送信、中間バッファーなし)
非同期I/O呼び出しを実行したい(非同期待機を使用)-しかし:
- DataFlow依存関係を使用せずに(この回答のように)
- ミドルバッファなし(この回答とは異なります)
- Projector関数は引数として送信する必要があります。 (この答えは好きではありません)
Stephen Toubの "Tasks、Monads、and LINQ" を調べて、非同期データシーケンスの処理方法に関する優れたアイデアを確認してください。
(まだ)yield
とawait
を組み合わせることはできませんが、ここでは言葉使いになります。引用された要件にはIEnumerable
とLINQは含まれていません。したがって、これが2つのコルーチン(ほとんどテストされていない)の形をした可能な解決策です。
データプロデューサールーチン(IEnumarable
とyield
に対応):
public async Task GetSomeDataAsync<T>(
string sql, Func<IDataRecord, T> projector, ProducerConsumerHub<T> hub)
{
using (SqlConnection _conn = new SqlConnection(@"Data Source=..."))
{
using (SqlCommand _cmd = new SqlCommand(sql, _conn))
{
await _conn.OpenAsync();
_cmd.CommandTimeout = 100000;
using (var rdr = await _cmd.ExecuteReaderAsync())
{
while (await rdr.ReadAsync())
await hub.ProduceAsync(projector(rdr));
}
}
}
}
データコンシューマールーチン(foreach
またはLINQ式に対応):
public async Task ConsumeSomeDataAsync(string sql)
{
var hub = new ProducerConsumerHub<IDataRecord>();
var producerTask = GetSomeDataAsync(sql, rdr => rdr, hub);
while (true)
{
var nextItemTask = hub.ConsumeAsync();
await Task.WhenAny(producerTask, nextItemTask);
if (nextItemTask.IsCompleted)
{
// process the next data item
Console.WriteLine(await nextItemTask);
}
if (producerTask.IsCompleted)
{
// process the end of sequence
await producerTask;
break;
}
}
}
コルーチン実行ヘルパー( custom awaiters のペアとして実装することもできます):
public class ProducerConsumerHub<T>
{
TaskCompletionSource<Empty> _consumer = new TaskCompletionSource<Empty>();
TaskCompletionSource<T> _producer = new TaskCompletionSource<T>();
// TODO: make thread-safe
public async Task ProduceAsync(T data)
{
_producer.SetResult(data);
await _consumer.Task;
_consumer = new TaskCompletionSource<Empty>();
}
public async Task<T> ConsumeAsync()
{
var data = await _producer.Task;
_producer = new TaskCompletionSource<T>();
_consumer.SetResult(Empty.Value);
return data;
}
struct Empty { public static readonly Empty Value = default(Empty); }
}
これは単なるアイデアです。これは、このような単純なタスクではやり過ぎになる可能性があり、一部の領域(スレッドセーフ、競合状態、producerTask
に触れずにシーケンスの最後を処理するなど)で改善される可能性があります。それでも、非同期データの取得と処理を分離できる可能性があることを示しています。
この 中程度の記事 は、Dasync/AsyncEnumerable
ライブラリを使用する別の解決策について説明しています。
ライブラリはオープンソースであり、 NuGet および GitHub で利用でき、IAsyncEnumerable
で C#8.0まで)使用できる読み取り可能な構文を提供します。が出て、独自の実装と言語サポートを提供しますasync ... yield return
およびawait foreach
の形式で。
(私はライブラリとは関係がありません。私が開発しているプロジェクトで、あなたと同じ問題に対して、非常に有用な解決策としてライブラリに出くわしました。)