在同一个实例处于进程中时,防止调用异步方法

时间:2018-05-31 10:48:23

标签: c# async-await

在我的Web应用程序中,我创建了一个方法,根据传递的URL创建GET请求,然后使用HttpClient创建GET调用。问题是一些GET调用可能需要很长时间才能完成,当该请求正在处理时,用户可以再次请求相同的URL,当然还有另一个GET调用同一个URL,例如用户可以调用" www.google.com"很多时候,前呼叫正在进行中,尚未完成。

我想阻止这种行为,我希望如果在进程中有相同的GET调用,则阻止对同一URL的任何进一步的GET调用。这是我的代码,但我不知道如何实现这个目标:

    var request = new RequestInfo(URL) { HttpMethod = "GET" };

    //here if the same URL is under process I want to prevent calling request.Send()
    if (await request.Send())
         {
           // other codes
         }

2 个答案:

答案 0 :(得分:1)

似乎很明显,需要某种挂号呼叫注册表。 不过,在实际实施之前你应该考虑一些事情。

此方法是否应该是线程安全的? 它应该,如果它将同时从多个线程调用。

如果不存在多线程问题,那么简单的Dictionary<string, Task>(您可能希望在此处使用Task<T>)就足够了。这是一个可以获得想法的小片段。

private readonly IDictionary<string, Task> _requestRegistry = new Dictionary<string, Task>();

Task MakeRequestAsync(string url)
{
    if (_requestRegistry.TryGetValue(url, out var existingTask))
    {
        return existingTask;
    }

    var request = new RequestInfo(url) { HttpMethod = "GET" };
    var responseTask = request.Send();

    _requestRegistry.Add(url, responseTask);

    return responseTask;
}

对于并发情况,实现将稍微复杂一些,只要有必要避免共享注册表字段的竞争条件。我们可能希望使用现有仪器而不是手动锁定,因此我们将在此处使用ConcurrentDictionary类型。代码现在可能与此类似。

private readonly ConcurrentDictionary<string, Task> _requestRegistry = new ConcurrentDictionary<string, Task>();

Task MakeRequestAsync(string url)
{
    return _requestRegistry.GetOrAdd(url, _ =>
    {
        var request = new RequestInfo(url) { HttpMethod = "GET" };
        return request.Send();
    });
}

但实际上这种方法存在一个小问题 - 您的委托可能会被调用两次,因为当前的并发字典实现会在进行任何锁定之前调用valueFactory,您可以从source code看到。您应该确定它是否适用于您的方案。

幸运的是,这个问题也有一个简洁而简单的黑客攻击 - 我们的valueFactory应该变得懒惰。在这种情况下,仍然可以调用valueFactory两次,但只有Web请求。

private readonly ConcurrentDictionary<string, Lazy<Task>> _requestRegistry = new ConcurrentDictionary<string, Lazy<Task>>();

Task MakeRequestAsync(string url)
{
    var lazyRequest = _requestRegistry.GetOrAdd(url, _ => new Lazy<Task>(() =>
    {
        var request = new RequestInfo(url) {HttpMethod = "GET"};
        return request.Send();
    }));

    return lazyRequest.Value;
}  

您认为结果会变得陈旧吗?

如果答案是肯定的,那么由于年龄的原因退回登记处的现有答复是不安全的,只有待处理的任务可能被认为是健康的。这意味着您应该在完成时删除任务。片段再次获得想法

request.Send()
    .ContinueWith(_ =>
    {
        _requestRegistry.Remove(url);
    });

您是否希望网址数量有限?

如果没有,将需要实施一些缓存驱逐策略。否则,您可能会为注册表使用过多的内存。

答案 1 :(得分:0)

简单的解决方案就是这样。

    public static Dictionary<string, object> URLS = new Dictionary<string, object>();

    static void Main(string[] args)
    {
        Console.WriteLine("Strat");
        SendRequest("www.google.com");
        Console.WriteLine("1");
        SendRequest("www.google.com");
        Console.WriteLine("2");
        Console.ReadKey();
    }

    public static async Task SendRequest(string url)
    {
        if (URLS.ContainsKey(url))
        {
            lock(URLS[url])
            {
                Task.Delay(5000).Wait();
                //GETASYNC();
            }
        }
        else
        {
            lock (URLS)
            {
                URLS.Add(url, new object());
                //GETASYNC();
            }
        }
    }