async & await - dealing with multiple calls to same method - locking/waiting on each other?

扶醉桌前 提交于 2021-02-08 03:38:06

问题


I had a complex Task/lock based mess for performing a 'long' data operation, and I'm trying to replace it with an async/await. I'm new to async await, so I'm worried I'm making some big mistakes.

To simplify things, my UI has a few pages that depend on the same data. Now, I only need to get this data once. So I cache it, and further calls just grab it from the cache "CachedDataObjects" rather than doing the long call every time.

Like this (semi-pseudocode):

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

However, by the nature of the asynchronous calls, this method get's called by the two pages when they are triggered.

As a result, there's an exception - an item with the ID has already been added to the dictionary. Even if I patched that problem, the underlying issue is still there. The Data objects would be different versions, I'm doing two network calls where I should only have one etc.

Previously, I 'hacked' a solution by encapsulating the whole method in a lock statement - thereby only allowing a single call to it. All my data loading being done in background workers, the first did the call, and once it had finished the others were then unlocked to perform the quick grab.

But I can't use lock in an asynchronous method, and the solution didn't feel good anyway.

Is there any way with asynchronous methods to 'wait' on other asynchronous calls to finish?


回答1:


Your problem is that you're awaiting the task before adding it to the dictionary. In this case, you'll want to add the task to the dictionary, so that the next page calling this method will get the same 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;
}

This will cache the task. However, if //deal with errors propagates exceptions, then this will cache that exception as well.

To avoid this, you can use more complex code, or you can adopt my AsyncLazy<T> 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;
}


来源:https://stackoverflow.com/questions/38635349/async-await-dealing-with-multiple-calls-to-same-method-locking-waiting-on

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!