One of my frustrations with Silverlight has been that the async model is rammed down your throat. I understand the reasoning that it is good to have the UI thread responsive while long running calls are happening. But to me that is something that I code and I control.
The thing that gets me is that there are times where several network operations may have to happen in sequence. Here is a simple contrived example:
var service = new VehicleRentalService();
if (service.AreVehiclesAvailableToRent())
{
txtNumberOfCars.Text = service.GetNumberOfCarsForRent();
if (service.IsTheFlakyOnlineRentalServiceRunning())
{
btnBookOnline.Enabled = true;
}
}
Now for the above example I would run all this on 1 background thread (leaving the UI nice and responsive) and make sure that I properly delegate the control changes to the UI thread (as controls are not thread safe).
Silverlight forces us to async the calls which leads to a lot of functions that chain together. I find this makes the code a lot harder to read and a lot harder to understand. Here is a hand written rewrite of the above example:
public function Start()
{
var service = new VehicleRentalService();
service.AreVehiclesAvailableToRentCompleted+=VehiclesAvailableToRentResponseRecieved;
service.GetNumberOfCarsForRentCompleted+=NumberOfCarsForRentResponseRecieved;
service.IsTheFlakyOnlineRentalServiceRunningCompleted+=IsTheFlakyOnlineRentalServiceRunningResponseRecieved;
service.AreVehiclesAvailableToRent();
}
public function VehiclesAvailableToRentResponseRecieved(object sender, VehiclesAvailableToRentCompletedEventArgs e)
{
if (e.Result)
{
service.GetNumberOfCarsForRent();
service.IsTheFlakyOnlineRentalServiceRunning();
}
}
public function NumberOfCarsForRentResponseRecieved(object sender, NumberOfCarsForRentCompletedEventArgs e)
{
Dispatcher.BeginInvoke(delegate { txtNumberOfCars.Text = e.Result });
}
public function IsTheFlakyOnlineRentalServiceRunningResponseRecieved(object sender, IsTheFlakyOnlineRentalServiceRunningResponseRecievedCompletedEventArgs e)
{
Dispatcher.BeginInvoke(delegate { btnBookOnline.Enabled = e.Result });
}
(Some may argue that there should be a GetSystemStatus method on the service that returns all this info to which I would probably agree. For the sake of this post though I am going to run with it).
So the Silverlight code is longer, harder to read, and harder to maintain. It does have the benefit of getting the number of cars for rent and checking if the online booking system is running at the same time which is a nice plus.
For an example a log more complicated than this one I was using a WebRequest to converse with a server in a very back and forth fashion and found that the code quickly became very hard to read so I set about to change things. My idea was to create a class that uses async calls in the background but appear to be synchronous by blocking the caller. i.e.
string customerData = Request.GetResponse(“http://www.dummy.com/getcustomer.aspx”, “CustomerId=1″);
string customerAddress = Request.GetResponse(“http://www.dummy.com/getaddress.aspx”, ParseOutAddressId(customerData));
PLEASE DO NOT USE THIS CODE AS IT DOES NOT WORK (hence the post title):
public class SyncRequestResponse
{
ManualResetEvent _requestCompleted;
private WebRequest _request;
private string _postData;
private Uri _requestUrl;
private static string _result;
public string GetResponse(Uri requestUrl, string postData)
{
_requestCompleted=new ManualResetEvent(false);
_requestCompleted.Reset();
_postData = postData;
_requestUrl = requestUrl;
StartRequest();
_requestCompleted.WaitOne(1000); //block current thread until the response is received or the timeout is reached
return _result; //the result should be filled now
}
private void StartRequest()
{
_request = WebRequest.Create(_requestUrl);
_request.ContentType = “application/x-www-form-urlencoded”;
_request.Method = “POST”;
_request.BeginGetRequestStream(GetRequestStreamCompleted, null);
}
private void GetRequestStreamCompleted(IAsyncResult result)
{
//request stream has returned so fill it
var content = Encoding.UTF8.GetBytes(_postData);
var stream = _request.EndGetRequestStream(result );
stream.Write(content, 0, content.Length);
stream.Close();
_request.BeginGetResponse(GetResponseFromRequest, null);
}
private void GetResponseFromRequest(IAsyncResult ar)
{
var response = _request.EndGetResponse(ar);
var sr = new StreamReader(response.GetResponseStream());
_result = sr.ReadToEnd();
_requestCompleted.Set();
}
}
What I quickly found was that the second my WaitOne() line ran was that the background methods would also hang. After a lot of mucking about and some research I found out one very important thing. The WebRequest and WebResponse methods call interacts with the browsers plugin API which runs on…. the UI thread. So by blocking the UI thread it also blocks any calls to the plugin API which blocks my WebRequest methods.
So to solve this I could probably place my sync wrapper onto a background thread to implement the back and forth conversations and then raise an event when it is done but after mucking around it just makes things too complex to read, follow, and maintain. It may still have its uses one day… but it was not as clean as I would like
Solution?
My main issue is when you have async methods that change so for WebRequest calls I decided to inline the callback methods it used. Again it is ugly but I find it simpler to follow a linear action:
public string GetCustomerDataAndBindItToTextBox(Uri requestUrl, string postData)
{
var request = WebRequest.Create(requestUrl);
request.ContentType = “application/x-www-form-urlencoded”;
request.Method = “POST”;
request.BeginGetRequestStream(delegate(IAsyncResult result)
{
byte[] content = Encoding.UTF8.GetBytes(postData);
Stream stream = request.EndGetRequestStream(result);
stream.Write(content, 0, content.Length);
stream.Close();
request.BeginGetResponse(delegate (IAsyncResult result2)
{
WebResponse response = request.EndGetResponse(result2);
var sr = new StreamReader(response.GetResponseStream());
this.txtCustomer.Text = sr.ReadToEnd();
}, null);
}, null);
}
Recent Comments