I want to check if a host can be resolved,but I don't want to wait a long time; so I want to set a timeout.
I have tried
public static bool ResolveTest(string hostNameOrAddress, TimeSpan time_out)
{
var result = Dns.BeginGetHostEntry(hostNameOrAddress, null, null);
var success = result.AsyncWaitHandle.WaitOne(time_out);
if (!success) {
//throw new Exception("Failed to resolve the domain.");
return false;
}
return true;
}
but this is not working correctly, because when it's a wrong host, it also can return true. So how to set a timeout for DnsResolve?
Original Answer
look at the updated answer
There is no way of actually cancelling the Dns.BeginGetHostEntry()
but you could try to implement something that can be cancelled after a set time. This is slightly hackish but can do as you have asked.
However DNS lookups are (typically) really fast and in tests typically resolve within 10ms.
Here is a sample. Basically making a Task
based approach that allows you to execute a Task
which returns a bool whether the result was completed.
public static Task<bool> Resolve(string hostNameOrAddress, int millisecond_time_out)
{
return Task.Run(async () =>
{
bool completed = false;
var asCallBack = new AsyncCallback(ar =>
{
ResolveState context = (ResolveState)ar.AsyncState;
if (context.Result == ResolveType.Pending)
{
try
{
var ipList = Dns.EndGetHostEntry(ar);
if (ipList == null || ipList.AddressList == null || ipList.AddressList.Length == 0)
context.Result = ResolveType.InvalidHost;
else
context.Result = ResolveType.Completed;
}
catch
{
context.Result = ResolveType.InvalidHost;
}
}
completed = true;
});
ResolveState ioContext = new ResolveState(hostNameOrAddress);
var result = Dns.BeginGetHostEntry(ioContext.HostName, asCallBack, ioContext);
int miliCount = 0;
while (!completed)
{
miliCount++;
if (miliCount >= millisecond_time_out)
{
result.AsyncWaitHandle.Close();
result = null;
ioContext.Result = ResolveType.Timeout;
break;
}
await Task.Delay(1);
}
Console.WriteLine($"The result of {ioContext.HostName} is {ioContext.Result}");
return ioContext.Result == ResolveType.Completed;
});
}
public class ResolveState
{
public ResolveState(string hostName)
{
if (string.IsNullOrWhiteSpace(hostName))
throw new ArgumentNullException(nameof(hostName));
_hostName = hostName;
}
readonly string _hostName;
public ResolveType Result { get; set; } = ResolveType.Pending;
public string HostName => _hostName;
}
public enum ResolveType
{
Pending,
Completed,
InvalidHost,
Timeout
}
The method can then be called as
public static async void RunTest()
{
await Resolve("asdfasdfasdfasdfasdfa.ca", 60);
await Resolve("google.com", 60);
}
When the Resolve is executed it creates an internal AsyncCallBack
delegate. This delegate is then passed to the Dns.BeginGetHostEntry()
method. In addition we pass in a state
object of ResolveState
. This state object is resposible for managing the state between the contexts. Therefore when the AsyncCallBack
gets executed we can set the Result
of the ResolveState
. Next the completed
flag is set to indicate the callback delegate completed.
Finally (and this is the part I dont like) is we then have a loop that executes every 1ms
and if the timeout expires sets the Result
of our state object to Timeout
.
Again this is slightly hackish but does result in sort of the desired effect. Now this is also done async
so you can use the async
& await
features.
Updated Answer
After the first answer I didnt like the result and reviewed your initial question. You were on the right track except we need to check the complete flag as you have done. However success
will return false if result
didn't finish after the WaitOne
call. If it finished then success
will be true
(even without resolving the IP address) you then need to check the result using the Dns.EndGetHostEntry
.
Revised Code:
public static bool ResolveTest(string hostNameOrAddress, int millisecond_time_out)
{
ResolveState ioContext = new ResolveState(hostNameOrAddress);
var result = Dns.BeginGetHostEntry(ioContext.HostName, null, null);
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(millisecond_time_out), true);
if (!success)
{
ioContext.Result = ResolveType.Timeout;
}
else
{
try
{
var ipList = Dns.EndGetHostEntry(result);
if (ipList == null || ipList.AddressList == null || ipList.AddressList.Length == 0)
ioContext.Result = ResolveType.InvalidHost;
else
ioContext.Result = ResolveType.Completed;
}
catch
{
ioContext.Result = ResolveType.InvalidHost;
}
}
Console.WriteLine($"The result of ResolveTest for {ioContext.HostName} is {ioContext.Result}");
return ioContext.Result == ResolveType.Completed;
}
Here's the full gist https://gist.github.com/Nico-VanHaaster/35342c1386de752674d0c37ceaa65e00.
Current accepted answer is quite old and complex, nowadays you can easily use System.Threading.Tasks.Task.Run()
for this, this way:
private Task<IPAddress> ResolveAsync(string hostName) {
return System.Threading.Tasks.Task.Run(() => {
return System.Net.Dns.GetHostEntry(hostName).AddressList[0];
});
}
private string ResolveWithTimeout(string hostNameOrIpAddress) {
var timeout = TimeSpan.FromSeconds(3.0);
var task = ResolveAsync(hostNameOrIpAddress);
if (!task.Wait(timeout)) {
throw new TimeoutException();
}
return task.Result.ToString();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With