在Action中使用ForEachAsync并等待时不等待

Tod*_*odd 4 .net c# asynchronous entity-framework async-await

以下应返回"C",但返回"B"

using System.Data.Entity;
//...
var state = "A";
var qry = (from f in db.myTable select f);
await qry.ForEachAsync(async (myRecord) => {
   await DoStuffAsync(myRecord);
   state = "B";
});
state = "C";
return state;
Run Code Online (Sandbox Code Playgroud)

它不等待DoStuffAsync完成,state="C"运行然后state="B"执行(因为它内部仍在等待).

Tod*_*odd 8

那是因为ForEachAsync的实现不等待委托操作

moveNextTask = enumerator.MoveNextAsync(cancellationToken);
action(current);
Run Code Online (Sandbox Code Playgroud)

请参阅https://github.com/mono/entityframework/blob/master/src/EntityFramework/Infrastructure/IDbAsyncEnumerableExtensions.cs#L19

但那是因为,你不能等待一个动作,委托需要是一个返回任务的Func - 请参阅如何实现异步动作委托方法?

因此,在Microsoft提供包含Func委托的签名并使用await调用它之前,您必须滚动自己的扩展方法.我现在正在使用以下内容.

public static async Task ForEachAsync<T>(
    this IQueryable<T> enumerable, Func<T, Task> action, CancellationToken cancellationToken) //Now with Func returning Task
{
    var asyncEnumerable = (IDbAsyncEnumerable<T>)enumerable;
    using (var enumerator = asyncEnumerable.GetAsyncEnumerator())
    {

        if (await enumerator.MoveNextAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false))
        {
            Task<bool> moveNextTask;
            do
            {
                var current = enumerator.Current;
                moveNextTask = enumerator.MoveNextAsync(cancellationToken);
                await action(current); //now with await
            }
            while (await moveNextTask.ConfigureAwait(continueOnCapturedContext: false));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这样,OP中的原始测试代码将按预期工作.