【问题标题】:async & await - dealing with multiple calls to same method - locking/waiting on each other?异步和等待 - 处理对同一方法的多次调用 - 相互锁定/等待?
【发布时间】:2016-07-28 20:12:23
【问题描述】:

我有一个复杂的基于任务/锁的混乱来执行“长”数据操作,我正在尝试用异步/等待替换它。我是异步等待的新手,所以我担心我会犯一些大错误。

为了简化事情,我的 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;
    }

但是,由于异步调用的性质,该方法 get 被两个页面在触发时调用。

因此,出现了一个例外 - 具有 ID 的项目已添加到字典中。即使我修补了这个问题,潜在的问题仍然存在。 Data 对象将是不同的版本,我正在执行两个网络调用,而我应该只有一个等等。

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

但是我不能在异步方法中使用锁,反正解决方案感觉不好。

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

【问题讨论】:

  • 在这种情况下缓存有意义吗?
  • @PankajGupta 对不起,代码中的小错误。 “CachedDataObjects”现在用作字典。
  • ,抱歉没有得到。

标签: c# asynchronous


【解决方案1】:

您的问题是您在将任务添加到字典之前awaiting 任务。在这种情况下,您需要将 task 添加到字典中,以便调用此方法的下一页将获得相同的 task:

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&lt;T&gt; type

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;
}

【讨论】:

  • 不错的方法,缓存任务而不是结果。据我所知,为了始终避免缓存测试和分配之间的竞争条件,仍然需要锁定?
  • @grek40:如果您使用的是非并发字典并从多个线程访问此方法,那么是的,需要锁定。
  • 这是个好主意!
  • 两种方法都可以工作,但我认为我会使用 AsyncLazy,因为它看起来更整洁。尽管如此,我仍然在思考它。
  • @StephenCleary 我收到此错误“匿名函数转换为 void 返回委托不能返回值”知道如何解决这个问题吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-12-22
  • 1970-01-01
  • 2013-10-24
  • 2021-11-01
  • 1970-01-01
相关资源
最近更新 更多