web-dev-qa-db-ja.com

ForEachでasync-awaitを使用して「接続がMultipleActiveResultSetsをサポートしていない」を取得する

Dapper.SimpleCRUDを使用した次のコードがあります。

var test = new FallEnvironmentalCondition[] {
    new FallEnvironmentalCondition {Id=40,FallId=3,EnvironmentalConditionId=1},
    new FallEnvironmentalCondition {Id=41,FallId=3,EnvironmentalConditionId=2},
    new FallEnvironmentalCondition {Id=42,FallId=3,EnvironmentalConditionId=3}
};
test.ToList().ForEach(async x => await conn.UpdateAsync(x));

このコードを使用すると、次の例外が発生します。

InvalidOperationException:接続はMultipleActiveResultSetsをサポートしていません

私は各更新をawaitingしていることを理解していないので、なぜこのエラーが発生するのですか?.

注:接続文字列を制御できないため、MARSをオンにできません。

13
MotKohn

このコードは、リスト内の各項目のタスクを開始しますが、次のタスクを開始する前に、各タスクが完了するのを待機しません。各タスク内では、更新が完了するのを待ちます。試す

 Enumerable.Range(1, 10).ToList().ForEach(async i => await Task.Delay(1000).ContinueWith(t => Console.WriteLine(DateTime.Now)));

これは

    foreach (var i in Enumerable.Range(1, 10).ToList() )
    {
        var task = Task.Delay(1000).ContinueWith(t => Console.WriteLine(DateTime.Now));
    }

非非同期メソッドを使用している場合は、各タスクを待つのではなく、Wait()を実行する必要があります。例えば

    foreach (var i in Enumerable.Range(1, 10).ToList() )
    {
        var task = Task.Delay(1000).ContinueWith(t => Console.WriteLine(DateTime.Now));
        //possibly do other stuff on this thread
        task.Wait(); //wait for this task to complete
    }

複数のアクティブな結果セットを許可するには、接続文字列に属性MultipleActiveResultSetsを追加してtrueに設定する必要があります。

 "Data Source=MSSQL1;" & _  
    "Initial Catalog=AdventureWorks;Integrated Security=SSPI;" & _  
    "MultipleActiveResultSets=True"  

続きを読む: https://docs.Microsoft.com/en-us/dotnet/framework/data/adonet/sql/enabling-multiple-active-result-sets

34
vendettamit

問題はForEachメソッドでない非同期メソッドです。 はできませんラムダが返すタスクを待ちます。そのコードを実行すると、すべてのタスクが実行され、それらのいずれかの完了を待ちません。

一般的なポイント:ラムダを非同期としてマークしても、それに渡す同期メソッドが非同期で動作することにはなりません。

解決策:タスクの完了を待つforeachループを使用する必要があります。

例:foreach(var x in xs)await f(x);

必要に応じて、ヘルパーメソッドでラップできます。

(私はそれが古い質問であることを知っていますが、明確に答えられたとは思いません)

5
Rafael Reis

MARSにはいくつかの制限があり、オーバーヘッドもゼロではありません。次のヘルパーを使用して、更新を順次実行できます。

public static async Task WhenAllOneByOne<T>(this IEnumerable<T> source, Func<T, Task> process)
{
    foreach (var item in source)
        await process(item);
}

public static async Task<List<U>> WhenAllOneByOne<T, U>(this IEnumerable<T> source, Func<T, Task<U>> transform)
{
    var results = new List<U>();

    foreach (var item in source)
        results.Add(await transform(item));

    return results;
    // I would use yield return but unfortunately it is not supported in async methods
}

だからあなたの例は

await test.WhenAllOneByOne(conn.UpdateAsync);

私は通常、次のようにTask.WhenAllではなく2番目のヘルパーを呼び出します。

await Task.WhenAll(source.Select(transform)); // not MARS-safe
await source.WhenAllOneByOne(transform); // MARS-safe
2
Herman Kan