web-dev-qa-db-ja.com

DataReaderでasync / awaitを使用していますか? (中間バッファーなし!)

私の目標は単純です。非同期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);
            }
        }
    }
}
_

それで、プロジェクターとは何ですか?

各クラスには、recordIDataRecord)を取得してエンティティを作成する関数があります。

例:

_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;    }
}
_

したがって、ここでは、MyClassFactoryFuncになります

それで、私は現在それをどのように実行していますか?

_ 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>'はイテレータインターフェイスタイプではないため、イテレータブロックにすることはできません

しかし、この男はここにいた -:

enter image description here

どちらが[〜#〜]何を行うか[〜#〜]コンパイルします。

質問

私のコードを完全に非同期をサポートするように変換するにはどうすればよいですかIO呼び出し?

(条件:DataFlow依存関係なし、プロジェクター関数を引数として送信、中間バッファーなし)

16
Royi Namir

非同期I/O呼び出しを実行したい(非同期待機を使用)-しかし:

  • DataFlow依存関係を使用せずに(この回答のように)
  • ミドルバッファなし(この回答とは異なります)
  • Projector関数は引数として送信する必要があります。 (この答えは好きではありません)

Stephen Toubの "Tasks、Monads、and LINQ" を調べて、非同期データシーケンスの処理方法に関する優れたアイデアを確認してください。

(まだ)yieldawaitを組み合わせることはできませんが、ここでは言葉使いになります。引用された要件にはIEnumerableとLINQは含まれていません。したがって、これが2つのコルーチン(ほとんどテストされていない)の形をした可能な解決策です。

データプロデューサールーチン(IEnumarableyieldに対応):

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に触れずにシーケンスの最後を処理するなど)で改善される可能性があります。それでも、非同期データの取得と処理を分離できる可能性があることを示しています。

11
noseratio

この 中程度の記事 は、Dasync/AsyncEnumerableライブラリを使用する別の解決策について説明しています。

ライブラリはオープンソースであり、 NuGet および GitHub で利用でき、IAsyncEnumerableC#8.0まで)使用できる読み取り可能な構文を提供します。が出て、独自の実装と言語サポートを提供しますasync ... yield returnおよびawait foreachの形式で。

(私はライブラリとは関係がありません。私が開発しているプロジェクトで、あなたと同じ問題に対して、非常に有用な解決策としてライブラリに出くわしました。)

0
MikeBeaton