/// <summary> /// Attempts to resolve an IP address or fully qualified domain name /// into host IP addresses. /// </summary> /// <param name="addressOrFQDN">The IP address or FQDN.</param> /// <param name="noCache"> /// Optionally specify that the method is not to answer from the cache, /// even if the cache is enabled. /// </param> /// <returns>An empty result set if the lookup failed.</returns> public async Task <IEnumerable <IPAddress> > LookupAsync(string addressOrFQDN, bool noCache = false) { await SyncContext.Clear; // We can short-circuit things if the parameter is an IP address. if (NetHelper.TryParseIPv4Address(addressOrFQDN, out var address)) { return(new IPAddress[] { address }); } // Try to answer from the cache, if enabled. if (cache != null && !noCache) { lock (cache) { if (cache.TryGetValue(addressOrFQDN, out var answer)) { if (answer.TTD > SysTime.Now) { // The answer hasn't expired yet, so we'll return it. return(answer.Addresses); } } } } // We're going to submit requests to all DNS clients in parallel and // return replies from the first one that answers. var readyEvent = new AsyncAutoResetEvent(); var sync = new object(); var pending = clients.Length; var results = (IList <IPAddress>)null; foreach (var client in clients) { var task = Task.Run( async() => { try { var addresses = await client.Lookup(addressOrFQDN); if (addresses.Count > 0) { lock (sync) { if (results == null) { results = addresses; } } } Interlocked.Decrement(ref pending); readyEvent.Set(); } catch (ResponseException) { // $todo(jefflill): // // I wish the underlying [DnsClient] didn't throw exceptions. Perhaps // I could extend the implementation to implement [TryResolve()]. Interlocked.Decrement(ref pending); readyEvent.Set(); } }); } while (pending > 0) { await readyEvent.WaitAsync(); if (results != null) { return(results); } } return(null); }