我有一个从数据库中获取一些数据的异步方法。此操作相当昂贵,并且需要很长时间才能完成。因此,我想缓存方法的返回值。但是,异步方法可能会在其初始执行有机会返回并将其结果保存到缓存之前被调用多次,从而导致多次调用此昂贵的操作。
为了避免这种情况,我目前正在重用一个 Task
,如下所示:
public class DataAccess
{
private Task<MyData> _getDataTask;
public async Task<MyData> GetDataAsync()
{
if (_getDataTask == null)
{
_getDataTask = Task.Run(() => synchronousDataAccessMethod());
}
return await _getDataTask;
}
}
我的想法是,对 GetDataAsync
的初始调用将启动 Task
中的 synchronousDataAccessMethod
方法,以及对该方法的任何后续调用在 Task
完成之前将简单地等待已经运行的 Task
,自动避免多次调用 synchronousDataAccessMethod
。在私有(private) Task
完成后调用 GetDataAsync
将导致等待 Task
,这将立即返回其初始执行的数据。
这似乎可行,但我遇到了一些奇怪的性能问题,我怀疑这些问题可能与此方法有关。具体来说,即使未调用 synchronousDataAccessMethod
调用,在它完成后等待 _getDataTask
也需要几秒钟(并锁定 UI 线程)。
我是否滥用了 async/await?是否有我没有看到的隐藏问题?是否有更好的方法来完成所需的行为?
编辑
我是这样调用这个方法的:
var result = (await myDataAccessObject.GetDataAsync()).ToList();
可能跟结果没有立即枚举有关系?
最佳答案
如果你想在调用堆栈中进一步等待它,我想你想要这个:
public class DataAccess
{
private Task<MyData> _getDataTask;
private readonly object lockObj = new Object();
public async Task<MyData> GetDataAsync()
{
lock(lockObj)
{
if (_getDataTask == null)
{
_getDataTask = Task.Run(() => synchronousDataAccessMethod());
}
}
return await _getDataTask;
}
}
您的原始代码有可能发生这种情况:
- 线程 1 看到
_getDataTask == null
,并开始构建任务 - 线程 2 看到
_getDataTask == null
,并开始构建任务 - 线程 1 完成构建任务,任务开始,线程 1 等待该任务
- 线程 2 完成构建任务,该任务开始,线程 2 等待该任务
您最终运行了两个任务实例。
关于c# - 使用任务来避免多次调用昂贵的操作并缓存其结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25797301/