async/await (Dapper を使用した SQL Server からのデータのストリーミング) で使用した場合、IEnumerable を返すとどうなりますか?

2020 年 3 月の更新

.NET Core 3.0 (および 3.1) がリリースされ、非同期ストリームが完全にサポートされました。 Microsoft.Bcl.AsyncInterfaces は、それらのサポートを .NET Standard 2.0 および .NET Framework 4.6.1+ に追加しますが、健全性のために 4.7.2 を使用する必要があります。 .NET Standard 実装サポートのドキュメントで説明されているように

元の回答

ソース コードを確認すると、疑いがほぼ正しいことがわかります。 bufferedの場合 偽、QueryAsync 同期的にストリーミングします .

if (command.Buffered)
{
    var buffer = new List<T>();
    var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
    while (await reader.ReadAsync(cancel).ConfigureAwait(false))
    {
        object val = func(reader);
        if (val == null || val is T)
        {
            buffer.Add((T)val);
        }
        else
        {
            buffer.Add((T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture));
        }
    }
    while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent result sets */ }
    command.OnCompleted();
    return buffer;
}
else
{
    // can't use ReadAsync / cancellation; but this will have to do
    wasClosed = false; // don't close if handing back an open reader; rely on the command-behavior
    var deferred = ExecuteReaderSync<T>(reader, func, command.Parameters);
    reader = null; // to prevent it being disposed before the caller gets to see it
    return deferred;
}

コメントで説明されているように、 ReadAsync は使用できません 戻り値の型が IEnumerable であると予想される場合。そのため、C# 8 の非同期列挙型を導入する必要がありました。

ExecuteReaderSync のコードは次のとおりです:

private static IEnumerable<T> ExecuteReaderSync<T>(IDataReader reader, Func<IDataReader, object> func, object parameters)
{
    using (reader)
    {
        while (reader.Read())
        {
            yield return (T)func(reader);
        }
        while (reader.NextResult()) { /* ignore subsequent result sets */ }
        (parameters as IParameterCallbacks)?.OnCompleted();
    }
}

Read を使用します ReadAsync の代わりに .

C#8 非同期ストリームでは、これを書き換えて IAsyncEnumerable を返すことができます .言語バージョンを変更するだけでは問題は解決しません。

非同期ストリームに関する現在のドキュメントを考えると、これは次のようになります:

private static async IAsyncEnumerable<T> ExecuteReaderASync<T>(IDataReader reader, Func<IDataReader, object> func, object parameters)
{
    using (reader)
    {
        while (await reader.ReadAsync())
        {
            yield return (T)func(reader);
        }

        while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent result sets */ }
         command.OnCompleted();
        (parameters as IParameterCallbacks)?.OnCompleted();
    }
}

ぶううううう 非同期ストリームは、.NET Core でのみ機能するものの 1 つであり、おそらくまだ実装されていません。 Sharplab.io で書き込もうとしたところ、Kaboom. [connection lost, reconnecting…]


dapper のコンテキストでは具体的に 、はい:@Panagiotis による優れた回答で説明されているように、別の API が必要です。以下は答えではありません しかし、同じ課題に直面している実装者が考慮したいと思うかもしれない追加のコンテキストです。

私はまだこれを dapper 用に「スパイク」していません (ただし、持っている SE.Redis 用)、さまざまなオプションの間で迷っています:

<オール>
  • .NET Core 用の新しい API を追加のみ 、適切な非同期列挙型を返します
  • 破壊的変更 (「メジャー」など) として既存の API を完全に破壊し、非同期で列挙可能な型を返すように変更します
  • おそらく「1」を使用しますが、正当な理由により、2 番目のオプションが非常に魅力的であると言わざるを得ません。

    • 既存の API は、人々が期待していることをおそらく実行しない
    • 新しいコードでそれを使い始めてほしい

    しかし奇妙なのは、IAsyncEnumerable<T> の .NET Core 3.0 らしさです。 - 明らかに、Dapper は .NET Core 3.0 だけをターゲットにしているわけではありません。

    <オール>
  • 機能を .NET Core 3.0 に制限し、IAsyncEnumerable<T> を返す
  • ライブラリを制限する .NET Core 3.0 に変換し、IAsyncEnumerable<T> を返します
  • 以前のフレームワークの System.Linq.Async (「公式」ではありませんが、私たちの目的には十分公式です) に依存し、IAsyncEnumerable<T> を返します。
  • そうでないカスタムの列挙型を返す 実際には IAsyncEnumerable<T> (しかし、これは IAsyncEnumerable<T> を実装しています 可能な場合)、ステート マシンを手動で実装する - foreach のダック タイプの性質 カスタムの列挙可能な型が適切なメソッドを提供する限り、これは正常に機能することを意味します
  • たぶん オプション 3 を使用しますが、繰り返します:はい、何かを変更する必要があります。


    (これはコメントのはずです // 今のところ十分な評判ではありません )

    Marc Gravell は返信で IAsyncEnumerable<T> と述べています が望ましいですが、NET Core 3.0 に依存しているため、System.Linq.Async に依存する方がよい場合があります。 (これは「公式に十分」と見なすことができます)...

    このコンテキストで、https://github.com/Dasync/AsyncEnumerable が頭に浮かびました (MIT ライセンス):役立つことを目指しています

    RE:「C# 8.0 がリリースされるとどうなりますか?」 (よくある質問)