首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >防止在同一实例正在运行时调用异步方法

防止在同一实例正在运行时调用异步方法
EN

Stack Overflow用户
提问于 2018-05-31 18:48:23
回答 1查看 655关注 0票数 0

在我的Web应用程序中,我创建了一个方法,该方法根据传递的URL创建GET请求,然后使用HttpClient创建GET调用。问题是一些GET调用可能需要很长时间才能完成,而当请求正在处理时,用户可以再次请求相同的URL,当然还有另一个对相同的URL的GET调用,例如,用户可以多次调用"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
         }
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-05-31 21:27:15

这似乎很明显,挂起的呼叫需要某种类型的注册表。然而,在实际实现之前,您应该考虑一些事情。

此方法应该是线程安全的吗?,如果它将从多个线程并发调用,则应该是。

如果不是多线程问题,那么简单的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);
    });

您是否希望urls集有限?

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

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50622534

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档