0

I need to fetch my public IP address from one of the IP address provider URLs. The catch is that these services are not reliable so I must have fallback to different URLs. To obtain maximum performance, I want to initiate WebRequest to all service providers at the same time and consider the result of the one who replies first.

This is the code i wrote. It works absolutely fine. But I have used EventWaitHandle. I just want to know if this is the right way to do it or is it possible to do the same without using WaitHandle (using async/await only)?

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

Here is the MyResetEvent class:

internal class MyResetEvent : EventWaitHandle
{
    public MyResetEvent()
        : base(false, EventResetMode.ManualReset)
    {

    }
    public string Result { get; set; }
    public int Count { get; set; }
}
PC.
  • 6,870
  • 5
  • 36
  • 71

2 Answers2

4

You are overthinking this way too much. The TPL is there to help you, not fight you!

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

Why the double await? The Task.WhenAny returns a Task<Task<T>> by design.

Bas
  • 26,772
  • 8
  • 53
  • 86
  • Wow thats good. I was not aware of the Task Parallel Library. – PC. Apr 10 '15 at 04:47
  • `WhenAny` does not execute the tasks in parallel? If I provide 4 URLs out of which only the last URL is correct, it takes about 10 seconds to fetch the result. Otherwise its less than a second. – PC. Apr 10 '15 at 05:10
  • @PC. `WhenAny` doesn't execute anything, it just iterates the collection you give it. What you're observing might be [because of autodetecting proxy](http://stackoverflow.com/q/3128563/41071). – svick Apr 10 '15 at 14:28
0

@Bas's answer is right on (you should probably accept it actually), but I wanted to offer an even more terse alternative that uses my Flurl library:

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

Flurl.Http is backed by HttpClient, which is newer and generally preferable to WebClient, all other things being equal.

Community
  • 1
  • 1
Todd Menier
  • 37,557
  • 17
  • 150
  • 173