"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 来说是个问题,但这确实能满足我的需求。
在我的存储库代码中,我得到了一个一次性的数据库实例。然后我将它用于 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 来说是个问题,但这确实能满足我的需求。