使用 async-await 在 ForEach 中获取 "The connection does not support MultipleActiveResultSets"

Getting "The connection does not support MultipleActiveResultSets" in a ForEach with async-await

我有以下使用 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: The connection does not support MultipleActiveResultSets

我不明白我正在 await 进行每次更新,所以为什么会出现此错误。

注意:我无法控制连接字符串,因此无法打开 MARS。

您需要在连接字符串中添加属性 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

该代码为列表中的每个项目启动一个任务,但不等待每个任务完成后再启动下一个任务。在每个任务中,它等待更新完成。尝试

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

如果您使用的是非异步方法,则必须等待 (),而不是等待每个任务。 EG

    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
    }

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,如下:

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

问题是 ForEach 方法 不是 异步方法。它不会等待您的 lambda 返回的任务。 运行 该代码将触发每个任务,而不是等待其中任何一个完成。

一般要点:将 lambda 标记为异步不会使您将其传入的同步方法行为异步。

解决方案:您将需要使用等待任务完成的 foreach 循环。

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

如果您愿意,可以将其包装在辅助方法中。

(我知道这是一个老问题,但我认为没有得到明确的回答)