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