synchronizing webClient download (silverlight) - silverlight

so I have this function which gets called multiple times during my program.
//global variable
BitmapImage img;
private void LoadImageFile(string ImageName)
{
WebClient ImageClient = new WebClient();
ImageClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(ImageFileLoaded);
xmlClient.DownloadStringAsync(new Uri("/images/"+ImageName, UriKind.RelativeOrAbsolute));
}
void ImageFileLoaded(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
{
img.set = e.Result;
}
}
the following code uses the new value of "img" so I want it to start only after img has been assigned the new source but it seems that it runs before that happens

You want to use WebClient.OpenReadAsync() instead of WebClient.DownloadStringAsync() because you want to read a binary image, not a string.
Then when you get the stream, you call BitmapImage.SetSource() using that stream.

I would check out this blog by Jeremy Likness.
It uses corountines to help organise async requests. I have used this approach and have dealt with similar issues where I want actions to occur after several async tasks.

Related

Why this Asynchronous example works without Dispatcher nor Control.BeginInvoke?

I was testing an Asynchronous example I wrote in other post, I modified it to show some info in a textbox. what happened next I was not expecting. I don't know why it does not throw an exception when modifying a control from another thread. am I blind or why I don't see it?
here is the example, it works the same for silverlight and WinForms:
int rand=0;
public MainPage()
{
InitializeComponent();
}
public Func<Action<int, int>, Action<int>> DownloadDataInBackground = (callback) =>
{
return (c) =>
{
WebClient client = new WebClient();
Uri uri = new Uri(string.Format("https://www.google.com/search?q={0}", c));
client.DownloadStringCompleted += (s, e2) =>
{
callback(c, e2.Result.Length);
};
client.DownloadStringAsync(uri);
};
};
private void button1_Click(object sender, RoutedEventArgs e)
{
int callid = rand++;
Debug.WriteLine("Executing CallID #{0}", callid);
DownloadDataInBackground((c3, r3) =>this.textBox1.Text+=string.Format("The result for the callid {0} is {1} \n", c3, r3))(callid);
}
Tap the button pretty fast, it wont fail.
your help will be very appreciated.
Edit: added picture showing that windows forms always execute controls modification from the main thread, but, why if it is supposed to be another one?
The actual answer to why your code doesn't fail in the way you expect it to is that the WebClient invokes its events on the UI thread. Hence you aren't modifying your control on a different thread as you seem to imagine you are.

Bitmap to byte[]

I have a problem with saving an image (or a bitmapImage or a PhotoResult) to a byte[] and then converting it back to an image.
I found a lot of posts on the internet about it but they dont work. In this code I got an Unspecifed error when I do this: SetSource ( bitmapImage.SetSource(ms);) and dont know how to do that.
I also want to make a list of Devices (each with a name, id, status and an image which I will represent as a byte[]) and save it to IsolatedStorage, and then read it and list them (with an image of course.)
Here is some code I have so far:
public void photoChooserTask_Completed(object sender, PhotoResult e)
{
if (e.TaskResult == TaskResult.OK)
{
imageBytes = new byte[e.ChosenPhoto.Length];
e.ChosenPhoto.Read(imageBytes, 0, imageBytes.Length);
BitmapImage bitmapImage = new BitmapImage();
MemoryStream ms = new MemoryStream(imageBytes);
try
{
bitmapImage.SetSource(ms);
}
catch (Exception ea)
{
//
}
image1.Source = bitmapImage;
}
Have you tried the Microsoft.Phone.PictureDecoder class? It has a DecodeJpeg function that returns an instance of a WritableBitmap object.
Another solution is to use the WritableBitmapEx extension library that makes digital image processing much easier and has a very good performance. The function you need is called FromByteArray.
In both cases you'll have to use a WriteableBitmap, because BitmapImage is protected from modification. Since both BitmapImage and WriteableBitmap are subclasses of BitmapSource, you can easily display them in the image control.
Hope it helps!

Loading an image in a background thread in WPF

There are a bunch of questions about this already on this site and other forums, but I've yet to find a solution that actually works.
Here's what I want to do:
In my WPF app, I want to load an image.
The image is from an arbitrary URI on the web.
The image could be in any format.
If I load the same image more than once, I want to use the standard windows internet cache.
Image loading and decoding should happen synchronously, but not on the UI Thread.
In the end I should end up with something that I can apply to an <Image>'s source property.
Things I have tried:
Using WebClient.OpenRead() on a BackgroundWorker. Works fine, but doesn't use the cache. WebClient.CachePolicy only affects that particular WebClient instance.
Using WebRequest on the Backgroundworker instead of WebClient, and setting WebRequest.DefaultCachePolicy. This uses the cache properly, but I've not seen an example that doesn't give me corrupted-looking images half the time.
Creating a BitmapImage in a BackgroundWorker, setting BitmapImage.UriSource and trying to handle BitmapImage.DownloadCompleted. This seems to use the cache if BitmapImage.CacheOption is set, but there doesn't seem to be away to handle DownloadCompleted since the BackgroundWorker returns immediately.
I've been struggling with this off-and-on for literally months and I'm starting to think it's impossible, but you're probably smarter than me. What do you think?
I have approached this problem in several ways, including with WebClient and just with BitmapImage.
EDIT: Original suggestion was to use the BitmapImage(Uri, RequestCachePolicy) constructor, but I realized my project where I tested this method was only using local files, not web. Changing guidance to use my other tested web technique.
You should run the download and decoding on a background thread because during loading, whether synchronous or after download the image, there is a small but significant time required to decode the image. If you are loading many images, this can cause the UI thread to stall. (There are a few other intricacies here like DelayCreation but they don't apply to your question.)
There are a couple ways to load an image, but I've found for loading from the web in a BackgroundWorker, you'll need to download the data yourself using WebClient or a similar class.
Note that BitmapImage internally uses a WebClient, plus it has a lot of error handling and settings of credentials and other things that we'd have to figure out for different situations. I'm providing this snippet but it has only been tested in a limited number of situations. If you are dealing with proxies, credentials, or other scenarios you'll have to massage this a bit.
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
Uri uri = e.Argument as Uri;
using (WebClient webClient = new WebClient())
{
webClient.Proxy = null; //avoids dynamic proxy discovery delay
webClient.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
try
{
byte[] imageBytes = null;
imageBytes = webClient.DownloadData(uri);
if (imageBytes == null)
{
e.Result = null;
return;
}
MemoryStream imageStream = new MemoryStream(imageBytes);
BitmapImage image = new BitmapImage();
image.BeginInit();
image.StreamSource = imageStream;
image.CacheOption = BitmapCacheOption.OnLoad;
image.EndInit();
image.Freeze();
imageStream.Close();
e.Result = image;
}
catch (WebException ex)
{
//do something to report the exception
e.Result = ex;
}
}
};
worker.RunWorkerCompleted += (s, e) =>
{
BitmapImage bitmapImage = e.Result as BitmapImage;
if (bitmapImage != null)
{
myImage.Source = bitmapImage;
}
worker.Dispose();
};
worker.RunWorkerAsync(imageUri);
I tested this in a simple project and it works fine. I'm not 100% about whether it is hitting the cache, but from what I could tell from MSDN, other forum questions, and Reflectoring into PresentationCore it should be hitting the cache. WebClient wraps WebRequest, which wraps HTTPWebRequest, and so on, and the cache settings are passed down each layer.
The BitmapImage BeginInit/EndInit pair ensures that you can set the settings you need at the same time and then during EndInit it executes. If you need to set any other properties, you should use the empty constructor and write out the BeginInit/EndInit pair like above, setting what you need before calling EndInit.
I typically also set this option, which forces it to load the image into memory during EndInit:
image.CacheOption = BitmapCacheOption.OnLoad;
This will trade off possible higher memory usage for better runtime performance. If you do this, then the BitmapImage will be loaded synchronously within EndInit, unless the BitmapImage requires async downloading from a URL.
Further notes:
BitmapImage will async download if the UriSource is an absolute Uri and is an http or https scheme. You can tell whether it is downloading by checking the BitmapImage.IsDownloading property after EndInit. There are DownloadCompleted, DownloadFailed, and DownloadProgress events, but you have to be extra tricky to get them to fire on the background thread. Since BitmapImage only exposes an asynchronous approach, you would have to add a while loop with the WPF equivalent of DoEvents() to keep the thread alive until the download is complete. This thread shows code for DoEvents that works in this snippet:
worker.DoWork += (s, e) =>
{
Uri uri = e.Argument as Uri;
BitmapImage image = new BitmapImage();
image.BeginInit();
image.UriSource = uri;
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriCachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
image.EndInit();
while (image.IsDownloading)
{
DoEvents(); //Method from thread linked above
}
image.Freeze();
e.Result = image;
};
While the above approach works, it has a code smell because of DoEvents(), and it doesn't let you configure the WebClient proxy or other things that might help with better performance. The first example above is recommended over this one.
The BitmapImage needs async support for all of its events and internals. Calling Dispatcher.Run() on the background thread will...well run the dispatcher for the thread. (BitmapImage inherits from DispatcherObject so it needs a dispatcher. If the thread that created the BitmapImage doesn't already have a dispatcher a new one will be created on demand. cool.).
Important safety tip: The BitmapImage will NOT raise any events if it is pulling data from cache (rats).
This has been working very well for me....
var worker = new BackgroundWorker() { WorkerReportsProgress = true };
// DoWork runs on a brackground thread...no thouchy uiy.
worker.DoWork += (sender, args) =>
{
var uri = args.Argument as Uri;
var image = new BitmapImage();
image.BeginInit();
image.DownloadProgress += (s, e) => worker.ReportProgress(e.Progress);
image.DownloadFailed += (s, e) => Dispatcher.CurrentDispatcher.InvokeShutdown();
image.DecodeFailed += (s, e) => Dispatcher.CurrentDispatcher.InvokeShutdown();
image.DownloadCompleted += (s, e) =>
{
image.Freeze();
args.Result = image;
Dispatcher.CurrentDispatcher.InvokeShutdown();
};
image.UriSource = uri;
image.EndInit();
// !!! if IsDownloading == false the image is cached and NO events will fire !!!
if (image.IsDownloading == false)
{
image.Freeze();
args.Result = image;
}
else
{
// block until InvokeShutdown() is called.
Dispatcher.Run();
}
};
// ProgressChanged runs on the UI thread
worker.ProgressChanged += (s, args) => progressBar.Value = args.ProgressPercentage;
// RunWorkerCompleted runs on the UI thread
worker.RunWorkerCompleted += (s, args) =>
{
if (args.Error == null)
{
uiImage.Source = args.Result as BitmapImage;
}
};
var imageUri = new Uri(#"http://farm6.static.flickr.com/5204/5275574073_1c5b004117_b.jpg");
worker.RunWorkerAsync(imageUri);

Problem with asynchronous webservice response

In my WP7 application i'm calling and consuming a webservice with these methods:
In my page .cs file:
public void Page_Loaded(object sender, RoutedEventArgs e)
{
if (NavigationContext.QueryString["val"] == "One")
{
listAgences=JSON.callWSAgence("http://...");
InitializeComponent();
DataContext = this;
}
}
In my json class i have these methods :
public List<Agence> callWSAgence(string url)
{
WebClient webClient = new WebClient();
Uri uri = new Uri(url);
webClient.OpenReadAsync(uri);
webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(OpenReadCompletedTestAgence);
return listAgences;
}
public void OpenReadCompletedTestAgence(object sender, OpenReadCompletedEventArgs e)
{
StreamReader reader = new System.IO.StreamReader(e.Result);
jsonResultString = reader.ReadToEnd().ToString();
addAgencesToList();
reader.Close();
}
public void addAgencesToList()
{
jsonResultString = json.Substring(5, json.Length - 6);
listAgences = JsonConvert.DeserializeObject<List<Agence>>(json);
}
The problem is that the OpenReadCompletedTest method in the json class is not called right after
webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(OpenReadCompletedTestAgence);
So the listAgences returned is empty.
But later OpenReadCompletedTest is called and everything works fined, but my view has already been loaded.
What can i do to have a kind of synchronous call or to reload my view after the webservice response being parsed and my list being filled.
The behaviour (problem) you are seeing is because the web request is made asynchronously.
If you want to have a separate object call the web server this will need to handle a callback to process the response or make appropriate changes itself.
Also:
- the code in the question doesn't show what the variable json is defined as. In Page_Loaded it looks like a custom class but in OpenReadCompletedTestAgence and addAgencesToList it looks like a string.
- the code in Page_Loaded sets the value of listAgences twice.
check out the following question for more information about making asychronous calls synchrously Faking synchronous calls in Silverlight WP7

Silverlight Web Service

Below is the code i used to consume web servcie in SilverLight.
private void button1_Click(object sender, RoutedEventArgs e)
{
BasicHttpBinding bind = new BasicHttpBinding();
EndpointAddress endpoint = new EndpointAddress("http://loalhost/Service.asmx");
ServiceSoapClient client = new ServiceSoapClient(bind, endpoint);
client.RunHelloCompleted += new EventHandler<RunHelloCompletedEventArgs>(client_RunQwinCompleted);
client.RunHelloAsync(command);
}
void client_RunHelloCompleted(object sender, RunHelloCompletedEventArgs e)
{
txtProcess.Text=Process(e.Result);
}
I want to know a way that after i run RunHelloAsync(Command), I want to get the returned result without going to Completed event. Please advise me. thank you.
Simple answer : You can't. Everything in Silverlight is Asynchronous so there is no way to block after the client.RunHelloAsync(command) call and wait for the result.
Long answer : There are ways to simulate working with calls in a synchronous fashion, but the calls still being made asynchronously. Take a look at this thread for a few more answers.

Resources