异步&;wait——处理对同一方法的多个调用——相互锁定/等待

本文关键字:调用 等待 锁定 amp wait 处理 异步 方法 | 更新日期: 2023-09-27 17:59:13

我在执行"长"数据操作时遇到了一个复杂的基于任务/锁的烂摊子,我正试图用async/await来替换它。我是异步等待的新手,所以我担心我会犯一些大错误。

为了简化,我的UI有几个页面依赖于相同的数据。现在,我只需要获取一次这些数据。所以我缓存它,并且进一步的调用只是从缓存"CachedDataObjects"中获取它,而不是每次都进行长调用。

像这样(半伪代码):

    private Dictionary<Guid,List<Data>> CachedDataObjects;
    public async Task<List<Data>> GetData(Guid ID)
    {
        List<Data> data = null;
        //see if we have any cached
        CachedDataObjects.TryGetValue(ID, out data);
        if (data == null)
        {
            if (ConnectedToServer)
            {
                data = new List<Data>();
                await Task.Run(() =>
                {
                    try
                    {
                        //long data call
                        data = Service.GetKPI(ID);
                    }
                    catch (Exception e)
                    {
                        //deal with errors (passes an action to do if resolved)
                        PromptForConnection(new Task(async () => { data = await GetData(ID); }), e);
                    }
                });
            }
            CachedDataObjects.Add(ID, data);
        }
        return data;
    }

然而,由于异步调用的性质,当两个页面被触发时,该方法将被调用。

结果,出现了一个异常——一个ID为的项已经添加到字典中。即使我解决了这个问题,根本问题仍然存在。数据对象将是不同的版本,我正在进行两次网络调用,其中我应该只有一次等。

以前,我通过将整个方法封装在一个锁语句中来"破解"一个解决方案,从而只允许对其进行一次调用。我所有的数据加载都是在后台工作人员中完成的,第一个完成了调用,一旦完成,其他人就被解锁以执行快速抓取。

但是我不能在异步方法中使用锁,而且这个解决方案感觉不太好。

异步方法有什么方法可以"等待"其他异步调用完成吗?

异步&;wait——处理对同一方法的多个调用——相互锁定/等待

您的问题是在将任务添加到字典之前先await。在这种情况下,您将希望将任务添加到字典中,以便调用此方法的下一个页面将获得相同的作业:

public Task<List<Data>> GetData(Guid ID)
{
  Task<List<Data>> task = null;
  CachedDataObjects.TryGetValue(ID, out task);
  if (task == null)
  {
    if (ConnectedToServer)
    {
      task = Task.Run(() =>
      {
        try
        {
          //long data call
          return Service.GetKPI(ID);
        }
        catch (Exception e)
        {
          //deal with errors
        }
      });
    }
    DataObjects.Add(ID, task);
  }
  return task;
}

这将缓存任务。但是,如果//deal with errors传播异常,那么它也会缓存该异常。

为了避免这种情况,你可以使用更复杂的代码,或者你可以采用我的AsyncLazy<T>类型:

private readonly ConcurrentDictionary<Guid, AsyncLazy<List<Data>>> CachedDataObjects;
public Task<List<Data>> GetData(Guid ID)
{
  var lazy = CachedDataObjects.GetOrAdd(ID, id =>
      new AsyncLazy<List<Data>>(() => Task.Run(() =>
      {
        try
        {
          return Service.GetKPI(ID);
        }
        catch (Exception e)
        {
          //deal with errors
          throw;
        }
      }, AsyncLazyFlags.RetryOnFailure | AsyncLazyFlags.ExecuteOnCallingThread)));
  return lazy.Task;
}