Запретить вызов асинхронного метода во время обработки того же экземпляра - PullRequest
0 голосов
/ 31 мая 2018

В моем веб-приложении я создал метод, который создает запрос GET в соответствии с переданным URL, а затем создает вызов GET с помощью HttpClient.Проблема в том, что некоторые вызовы GET могут занять много времени, и пока этот запрос обрабатывается, пользователь может снова запросить тот же URL-адрес, и, конечно, есть другой вызов GET с тем же URL-адресом, например, пользователь может позвонить "www.google..com "много раз, когда первый вызов обрабатывается и еще не завершен.

Я хочу предотвратить это поведение, я хочу, чтобы в процессе находился один и тот же вызов GET, чтобы предотвратить дальнейшие вызовы GET с тем же URL-адресом.Вот мой код, но я не знаю, как этого добиться:

    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 голосов
/ 31 мая 2018

Кажется довольно очевидным, что требуется какой-то реестр для ожидающих вызовов.Тем не менее, вы должны рассмотреть некоторые вещи перед фактической реализацией.

Должен ли этот метод быть потокобезопасным? Следует, если он будет вызываться из нескольких потоков одновременно.

Если проблемы с многопоточностью не таковы, то будет достаточно простого 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, прежде чем любая блокировка будет выполнена каквы можете видеть из исходного кода .Вы должны решить, является ли это проблемой для вашего сценария.

К счастью, для этой проблемы также есть хитрый и простой способ - наш valueFactory должен стать ленивым.В этом случае все еще возможно, что valueFactory будет вызван дважды, но с единственным веб-запросом.

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

Ожидаете ли вы ограниченный набор URL-адресов?

Потребуется реализовать некоторую политику удаления кэша, если нет,В противном случае вы можете использовать слишком много памяти для вашего реестра.

0 голосов
/ 31 мая 2018

Это простое решение.

    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();
            }
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...