Несколько асинхронных вызовов в одном методе. Правильный путь?
Мне нужно получить мой публичный IP-адрес с одного из URL-адресов провайдера IP-адресов. Суть в том, что эти сервисы ненадежны, поэтому я должен иметь запасной вариант для разных URL. Чтобы получить максимальную производительность, я хочу инициировать WebRequest для всех поставщиков услуг одновременно и рассмотреть результат того, кто ответит первым.
Это код, который я написал. Работает абсолютно нормально. Но я использовал EventWaitHandle
, Я просто хочу знать, если это правильный способ сделать это или это можно сделать без использования WaitHandle
(используя только async/await)?
private static readonly string[] IpProviders = new string[] {
"http://ipinfo.io/ip", "http://canihazip.com/s",
"http://icanhazip.com", "http://bot.whatismyipaddress.com" };
private static string _publicIp = null;
public static string PublicIp
{
get
{
if (_publicIp == null)
{
_publicIp = FetchPublicIp();
}
return _publicIp;
}
}
private static string FetchPublicIp()
{
using (MyResetEvent manualEvent = new MyResetEvent())
{
foreach (string providerUrl in IpProviders)
{
FetchPublicIp(providerUrl).
ContinueWith(x => OnResult(x.Result, manualEvent));
}
int looped = 0;
do
{
manualEvent.WaitOne();
lock (manualEvent)
{
if (!string.IsNullOrWhiteSpace(manualEvent.Result))
{
return manualEvent.Result;
}
else
{
manualEvent.Reset();
}
looped = manualEvent.Count;
}
} while (looped < IpProviders.Length);
}
return null;
}
private static async Task<string> FetchPublicIp(string providerUrl)
{
string externalip;
try
{
externalip = await new WebClient().DownloadStringTaskAsync(providerUrl);
}
catch (WebException ex)
{
Debug.WriteLine(ex);
externalip = null;
}
if (!string.IsNullOrWhiteSpace(externalip))
{
System.Net.IPAddress ip;
if (System.Net.IPAddress.TryParse(externalip.Trim(), out ip))
{
return ip.ToString();
}
}
return null;
}
private static void OnResult(string s, MyResetEvent manualEvent)
{
try
{
lock (manualEvent)
{
if (manualEvent.Result == null)
{
manualEvent.Result = s;
}
manualEvent.Count++;
manualEvent.Set();
}
}
catch (ObjectDisposedException ex)
{
Debug.WriteLine(ex);
}
}
Вот класс MyResetEvent:
internal class MyResetEvent : EventWaitHandle
{
public MyResetEvent()
: base(false, EventResetMode.ManualReset)
{
}
public string Result { get; set; }
public int Count { get; set; }
}
2 ответа
Вы слишком много думаете об этом. TPL там, чтобы помочь вам, а не бороться с вами!
async Task<string> TakeFirstResponse(string[] urls)
{
return await await Task.WhenAny(
urls.Select(async url =>
await new WebClient().DownloadStringTaskAsync(url)));
}
Почему двойного ждут? Task.WhenAny
возвращает Task<Task<T>>
по дизайну.
Ответ @ Баса правильный (вы, вероятно, должны принять его на самом деле), но я хотел предложить еще более краткую альтернативу, которая использует мою библиотеку Flurl:
async Task<string> TakeFirstResponse(string[] urls)
{
return await await Task.WhenAny(urls.Select(url => url.GetStringAsync()));
}
Flurl.Http поддерживается HttpClient
, который является более новым и, как правило, предпочтительнее WebClient
при прочих равных условиях.