迁移到异步:存储库
Migrating to Async: Repository
我有一个大型代码库使用我的存储库,它们都实现了 IRespository,并且我正在实现这些方法的异步版本:
T Find(id);
Task<T> FindAsync(id);
...etc...
存储库有多种。最简单的是基于一个不可变的集合,其中实体的范围足够小,值得一次从数据库中加载它们。此加载发生在任何人第一次调用任何 IRepository 方法时。例如,Find(4) 将在加载尚未发生时触发加载。
我已经用 Lazy < T > 实现了这个。非常好用,已经用了很多年了。
我不能在 Async 上使用冷火鸡,所以我必须在同步版本旁边添加 Async。我的问题是,我不知道先调用哪个 - 存储库上的同步或异步方法。
我不知道如何声明我的懒惰 - 如果我像往常一样这样做,
Lazy<MyCollection<T>>
然后在首次调用 FindAsync() 时加载它不会是异步的。另一方面,如果我去
Lazy<Task<MyCollection<T>>>
这对于 FindAsync() 非常有用,但是同步方法将如何触发初始加载 w/o 运行 与 Cleary 先生关于调用 Task.Result 的死锁警告相冲突?
感谢您的宝贵时间!
Task
s 只会 运行 一次,但你可以 await
任意多次,你也可以调用 Wait()
或 Result
完成后在它们上面,不会阻塞。
异步方法被转换为状态机,在可等待完成后,在每个 await
到 运行 之后调度代码。但是,有一个优化,如果 awaitable 已经完成代码 运行s 立即。因此,等待已完成的等待者的开销很小。
对于那些小型内存存储库,您可以使用 Task.FromResult
return 完成 Task
。您可以缓存任何 Task
并随时等待它。
how will a synchronous method trigger the initial load w/o running afoul of Mr. Cleary's warnings about deadlock from calling Task.Result?
您可以使用同步版本并使用 Task.FromResult
加载您的 Lazy<Task<MyCollection<T>>>
。如果这个惰性异步操作暴露给外部,它可能会造成混淆,因为它会阻塞。如果这是内部单次呼叫情况,我会选择:
private Lazy<Task<MyCollection<T>>> myCollection = new Lazy<Task<MyCollection<T>>>(() =>
{
var collection = myRepo.GetCollection<T>();
return Task.FromResult(collection);
}
Lazy<T>
的问题是只有一个工厂方法。如果第一次调用是同步的,你真正想要的是一个同步工厂方法,如果第一次调用是异步的,你真正想要的是一个异步工厂方法。 Lazy<T>
不会为你做那件事,据我所知,也没有其他内置的东西可以提供这些语义。
但是,您可以自己构建一个:
public sealed class SyncAsyncLazy<T>
{
private readonly object _mutex = new object();
private readonly Func<T> _syncFunc;
private readonly Func<Task<T>> _asyncFunc;
private Task<T> _task;
public SyncAsyncLazy(Func<T> syncFunc, Func<Task<T>> asyncFunc)
{
_syncFunc = syncFunc;
_asyncFunc = asyncFunc;
}
public T Get()
{
return GetAsync(true).GetAwaiter().GetResult();
}
public Task<T> GetAsync()
{
return GetAsync(false);
}
private Task<T> GetAsync(bool sync)
{
lock (_mutex)
{
if (_task == null)
_task = DoGetAsync(sync);
return _task;
}
}
private async Task<T> DoGetAsync(bool sync)
{
return sync ? _syncFunc() : await _asyncFunc().ConfigureAwait(false);
}
}
或者你可以只使用这个模式而不封装它:
private readonly object _mutex = new object();
private Task<MyCollection<T>> _collectionTask;
private Task<MyCollection<T>> LoadCollectionAsync(bool sync)
{
lock (_mutex)
{
if (_collectionTask == null)
_collectionTask = DoLoadCollectionAsync(sync);
return _collectionTask;
}
}
private async Task<MyCollection<T>> DoLoadCollectionAsync(bool sync)
{
if (sync)
return LoadCollectionSynchronously();
else
return await LoadCollectionAsynchronously();
}
"bool sync" 模式是 Stephen Toub 最近给我看的一个模式。据我所知,目前还没有博客或任何相关内容。
我有一个大型代码库使用我的存储库,它们都实现了 IRespository,并且我正在实现这些方法的异步版本:
T Find(id);
Task<T> FindAsync(id);
...etc...
存储库有多种。最简单的是基于一个不可变的集合,其中实体的范围足够小,值得一次从数据库中加载它们。此加载发生在任何人第一次调用任何 IRepository 方法时。例如,Find(4) 将在加载尚未发生时触发加载。
我已经用 Lazy < T > 实现了这个。非常好用,已经用了很多年了。
我不能在 Async 上使用冷火鸡,所以我必须在同步版本旁边添加 Async。我的问题是,我不知道先调用哪个 - 存储库上的同步或异步方法。
我不知道如何声明我的懒惰 - 如果我像往常一样这样做,
Lazy<MyCollection<T>>
然后在首次调用 FindAsync() 时加载它不会是异步的。另一方面,如果我去
Lazy<Task<MyCollection<T>>>
这对于 FindAsync() 非常有用,但是同步方法将如何触发初始加载 w/o 运行 与 Cleary 先生关于调用 Task.Result 的死锁警告相冲突?
感谢您的宝贵时间!
Task
s 只会 运行 一次,但你可以 await
任意多次,你也可以调用 Wait()
或 Result
完成后在它们上面,不会阻塞。
异步方法被转换为状态机,在可等待完成后,在每个 await
到 运行 之后调度代码。但是,有一个优化,如果 awaitable 已经完成代码 运行s 立即。因此,等待已完成的等待者的开销很小。
对于那些小型内存存储库,您可以使用 Task.FromResult
return 完成 Task
。您可以缓存任何 Task
并随时等待它。
how will a synchronous method trigger the initial load w/o running afoul of Mr. Cleary's warnings about deadlock from calling Task.Result?
您可以使用同步版本并使用 Task.FromResult
加载您的 Lazy<Task<MyCollection<T>>>
。如果这个惰性异步操作暴露给外部,它可能会造成混淆,因为它会阻塞。如果这是内部单次呼叫情况,我会选择:
private Lazy<Task<MyCollection<T>>> myCollection = new Lazy<Task<MyCollection<T>>>(() =>
{
var collection = myRepo.GetCollection<T>();
return Task.FromResult(collection);
}
Lazy<T>
的问题是只有一个工厂方法。如果第一次调用是同步的,你真正想要的是一个同步工厂方法,如果第一次调用是异步的,你真正想要的是一个异步工厂方法。 Lazy<T>
不会为你做那件事,据我所知,也没有其他内置的东西可以提供这些语义。
但是,您可以自己构建一个:
public sealed class SyncAsyncLazy<T>
{
private readonly object _mutex = new object();
private readonly Func<T> _syncFunc;
private readonly Func<Task<T>> _asyncFunc;
private Task<T> _task;
public SyncAsyncLazy(Func<T> syncFunc, Func<Task<T>> asyncFunc)
{
_syncFunc = syncFunc;
_asyncFunc = asyncFunc;
}
public T Get()
{
return GetAsync(true).GetAwaiter().GetResult();
}
public Task<T> GetAsync()
{
return GetAsync(false);
}
private Task<T> GetAsync(bool sync)
{
lock (_mutex)
{
if (_task == null)
_task = DoGetAsync(sync);
return _task;
}
}
private async Task<T> DoGetAsync(bool sync)
{
return sync ? _syncFunc() : await _asyncFunc().ConfigureAwait(false);
}
}
或者你可以只使用这个模式而不封装它:
private readonly object _mutex = new object();
private Task<MyCollection<T>> _collectionTask;
private Task<MyCollection<T>> LoadCollectionAsync(bool sync)
{
lock (_mutex)
{
if (_collectionTask == null)
_collectionTask = DoLoadCollectionAsync(sync);
return _collectionTask;
}
}
private async Task<MyCollection<T>> DoLoadCollectionAsync(bool sync)
{
if (sync)
return LoadCollectionSynchronously();
else
return await LoadCollectionAsynchronously();
}
"bool sync" 模式是 Stephen Toub 最近给我看的一个模式。据我所知,目前还没有博客或任何相关内容。