使用任务可以避免多次调用昂贵的操作并缓存其结果

Nat*_*end 1 c# multithreading task task-parallel-library async-await

我有一个异步方法从数据库中提取一些数据.此操作相当昂贵,并且需要很长时间才能完成.因此,我想缓存方法的返回值.但是,async方法可能会在其初始执行有机会返回并将其结果保存到缓存之前多次调用,从而导致对此昂贵操作的多次调用.

为了避免这种情况,我正在重复使用a Task,如下所示:

public class DataAccess
{
    private Task<MyData> _getDataTask;

    public async Task<MyData> GetDataAsync()
    {
        if (_getDataTask == null)
        {
            _getDataTask = Task.Run(() => synchronousDataAccessMethod());
        }

        return await _getDataTask;
    }
}
Run Code Online (Sandbox Code Playgroud)

我的想法是,初始调用GetDataAsync将启动synchronousDataAccessMethoda中的方法Task,并且在Task完成之前对此方法的任何后续调用将只是等待已经运行Task,自动避免synchronousDataAccessMethod多次调用.GetDataAsync在私有Task完成之后进行的调用将导致Task等待,这将立即从其初始执行返回数据.

这似乎有效,但我有一些奇怪的性能问题,我怀疑可能与这种方法有关.具体来说,等待_getDataTask完成后需要几秒钟(并锁定UI线程),即使synchronousDataAccessMethod未调用该调用.

我是否误用async/await?有没有隐藏的陷阱,我没有看到?有没有更好的方法来实现理想的行为?

编辑

以下是我调用此方法的方法:

var result = (await myDataAccessObject.GetDataAsync()).ToList();
Run Code Online (Sandbox Code Playgroud)

也许这与结果没有立即枚举的事实有关?

Jim*_*hel 6

如果你想等待它进一步调用堆栈,我想你想要这个:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

您的原始代码有可能发生这种情况:

  • 线程1看到了_getDataTask == null,并开始构建任务
  • 线程2看到了_getDataTask == null,并开始构建任务
  • 线程1完成构建任务的启动,并且线程1等待该任务
  • 线程2完成构建任务的启动,并且线程2等待该任务

最终会有两个正在运行的任务实例.