"using" 个具有 IAsyncEnumerable 的块

"using" blocks with IAsyncEnumerable

在我的存储库代码中,我得到了一个一次性的数据库实例。然后我将它用于 return 我的对象类型的 IAsyncEnumable 。我遇到的问题 运行 是数据库对象在枚举发生之前就被处理掉了——所以连接从它下面关闭了。解决这个问题的模式是什么? (如果它重要——它不应该——这是 NPoco)。

我正在编辑这个问题,说它是 IAsyncEnumerable 特有的,因此等待的逐行提取在这种情况下更合适,而不是组装整个结果列表和 return立即阅读。

    public IAsyncEnumerable<T_AccountViewProperty> RetrieveManyAsync(AccountViewId input)
    {
        var database = GetDatabase();
        return database.Query<T_AccountViewProperty>()
            .Where(x => x.AccountViewId == (int)input)
            .ToEnumerableAsync()
            .DisposeWhenCompleted(database);     // <-- is this a thing?
    }

不,DisposeWhenCompleted(database) 不是问题。但它可能是,如果你为它编写一个扩展方法。例如:

public static async IAsyncEnumerable<T> DisposeWhenCompleted<T>(
    this IAsyncEnumerable<T> source,
    IDisposable disposable)
{
    using (disposable)
    {
        await foreach (T t in source)
        {
            yield return t;
        }
    }
}

这正是您想要的。也就是说,我发现 using 语句在显式使用时更好。恕我直言,上面的构造并没有那么清晰,只是将 using 放在方法本身中,即使后者更冗长:

public IAsyncEnumerable<T_AccountViewProperty> RetrieveManyAsync(AccountViewId input)
{
    using (var database = GetDatabase())
    {
        var result = database.Query<T_AccountViewProperty>()
            .Where(x => x.AccountViewId == (int)input)
            .ToEnumerableAsync()

        await foreach (T_AccountViewProperty x in result)
        {
            yield return x;
        }
    }
}

另一个像上面那样做的好处是它更有效率,特别是当你想处理多个项目时(如果你有多个项目,你可以链接 DisposeWhenCompleted() 方法调用,但是为每个需要处理的项目创建一个新的迭代器)。

就像常规迭代器方法一样,C# 编译器在将代码重写为状态机时将确保 using 块创建的隐式 finally 在迭代之前不会执行实际上已经完成了。这实际上也适用于常规 async 方法,它与原始迭代器方法和新的 async 迭代器方法一样,具有与方法 [=31] 相同的 属性 =] 在方法中的代码实际完成之前发送给调用者。

这是有道理的,因为常规 async 方法及其对应的迭代器本质上都是原始迭代器方法的后代。三种方法都有一个基本概念,就是把方法改写成状态机。

根据 Peter Duniho 的回答,我最终采用的策略是这样的扩展方法:

public static class TaskExtensions
{
    public static Task<TResult> DisposeAfter<TResult>(this Task<TResult> input, IDisposable disposable)
    {
        TResult ContinuationFunction(Task<TResult> t)
        {
            using (disposable) return t.Result;
        }

        return input.ContinueWith(ContinuationFunction, TaskContinuationOptions.OnlyOnRanToCompletion);
    }
}

像这样使用:

    public Task<T_Account> RetrieveAsync(AccountId input)
    {
        var database = GetDatabase();
        return database.Query<T_Account>()
            .SingleAsync(x => x.AccountId == (int) input)
            .DisposeAfter(database);
    }

解决是否需要“OnlyOnRanToCompletion”对 Future Bryan 来说是个问题,但这确实能满足我的需求。