I\'m writing a WPF application and trying to bind an image to my view model with the following XAML:
Well I think I found out why it is happening...
I dug around with Reflector a bit to try to find out what exactly was getting called. Inside the BitmapDecoder I found a simple call to a WebRequest.BeginGetResponseStream.
I wrote a quick console app to test:
static void Main(string[] args)
{
DateTime start = DateTime.Now;
WebRequest request = WebRequest.Create("http://nonexistserver/myicon.jpg");
IAsyncResult ar = request.BeginGetResponse((AsyncCallback)delegate(IAsyncResult result)
{
try
{
WebResponse response = request.EndGetResponse(result);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}, null);
Console.WriteLine(DateTime.Now - start);
ar.AsyncWaitHandle.WaitOne();
Console.WriteLine("Done");
Console.ReadKey();
}
Without Fiddler running, the output is 2-3 seconds. With Fiddler running, the output is ~.25 seconds.
Doing some more digging, it looks like BeginGetResponse (which is what WPF is using under the hood) blocks until name resolution is complete.
See this question: webrequest.begingetresponse is taking too much time when the url is invalid
So I understand why the blocking occurs now, but I don't know a clean solution for my application. :(
Here is a new answer for you, hopefully better than my earlier one.
When you create your binding with 'IsAsync' true, it executes the property access to Author.IconUrl on a separate thread but does the conversion from Uri to ImageSource in the main thread. As you discovered, the conversion does a DNS lookup on the main thread causing the application to lock up.
Since your source is http/https, WPF will automatically handle asynchronously loading the image source. So I suspect all you need to do is to make just the DNS lookup asynchronous.
This can be automated by using an attached property:
<Image my:ImageAsyncHelper.SourceUri="{Binding Author.IconUrl}" />
where ImageAsyncHelper is defined as:
public class ImageAsyncHelper : DependencyObject
{
public static Uri GetSourceUri(DependencyObject obj) { return (Uri)obj.GetValue(SourceUriProperty); }
public static void SetSourceUri(DependencyObject obj, Uri value) { obj.SetValue(SourceUriProperty, value); }
public static readonly DependencyProperty SourceUriProperty = DependencyProperty.RegisterAttached("SourceUri", typeof(Uri), typeof(ImageAsyncHelper), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
((Image)obj).SetBinding(Image.SourceProperty,
new Binding("VerifiedUri")
{
Source = new ImageAsyncHelper { GivenUri = (Uri)e.NewValue },
IsAsync = true,
});
}
});
Uri GivenUri;
public Uri VerifiedUri
{
get
{
try
{
Dns.GetHostEntry(GivenUri.DnsSafeHost);
return GivenUri;
}
catch(Exception)
{
return null;
}
}
}
}
The way this works is: