同一方法中的多个异步调用。正确的方式?

时间:2015-04-10 04:16:34

标签: c# asynchronous async-await task manualresetevent

我需要从其中一个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 个答案:

答案 0 :(得分:4)

你过分夸大这种方式。 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>>

答案 1 :(得分:0)

@Bas的答案是正确的(你应该接受它实际上),但我想提供一个更简洁的替代方案,使用我的Flurl库:

async Task<string> TakeFirstResponse(string[] urls)
{
    return await await Task.WhenAny(urls.Select(url => url.GetStringAsync()));
}

Flurl.HttpHttpClient支持,WebClient更新,generally preferable支持{{1}},所有其他条件相同。