Waiting for async operation to complete - c#

I have a question about how asynchronous methods work on c#. I am trying to get the convertAddressToCoordinate method to set the myLocation variable to a GeoCoordinate. However, the compareDistance method is called even before the myLocation value is set. How can I ensure that the myLocation value is not null before I call compareDistance()?
public GeoCoordinate myLocation = null;
public void returnClosestCurrent(string address)
{
convertAddressToCoordinate(address);
compareDistance(myLocation);
}
public void convertAddressToCoordinate(string add)
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += wc_DownloadStringCompleted;
wc.DownloadStringAsync(new Uri("http://maps.googleapis.com/maps/api/geocode/json?address=1600+bay+st&sensor=false"));
}
void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
XDocument xdoc = XDocument.Parse(e.Result, LoadOptions.None);
var data = from query in xdoc.Descendants("location")
select new Location
{
lat = (string)query.Element("lat"),
lng = (string)query.Element("lng")
};
GeoCoordinate destinationGeo = new GeoCoordinate(Convert.ToDouble(data.ElementAt(0).lat), Convert.ToDouble(data.ElementAt(0).lng));
myLocation = destinationGeo;
}

you can use the await keyword for getting values of async task
http://msdn.microsoft.com/en-us/library/vstudio/hh156528.aspx

Looking at your code, seems to me that the compareDistance(myLocation); should be called only in the end of the wc_DownloadStringCompleted method.
After that change, everything should work fine.

You would want to await the wc.DownloadStringAsync
public async Task returnClosestCurrent(string address)
{
await convertAddressToCoordinate(address)
.ContinueWith(t => compareDistance(myLocation));
}
public async Task convertAddressToCoordinate(string add)
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += wc_DownloadStringCompleted;
await wc.DownloadStringAsync(new Uri("http://maps.googleapis.com/maps/api/geocode/json?address=1600+bay+st&sensor=false"));
}
The pattern with async/await is the code tends to turn everything into async/await
It could be refactored to use WebClient.DownloadStringTaskAsync
public async Task ReturnClosestCurrent(string address)
{
await convertAddressToCoordinate(address)
.ContinueWith(t => compareDistance(t.Result));
}
public async Task<GeoCoordinate> ConvertAddressToCoordinate(string add)
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += wc_DownloadStringCompleted;
var content = await wc.DownloadStringTaskAsync(new Uri("http://maps.googleapis.com/maps/api/geocode/json?address=1600+bay+st&sensor=false"));
return ParseContent(content);
}
private GeoCoordinate ParseContent(string content)
{
XDocument xdoc = XDocument.Parse(content, LoadOptions.None);
var data = from query in xdoc.Descendants("location")
select new Location
{
lat = (string)query.Element("lat"),
lng = (string)query.Element("lng")
};
GeoCoordinate destinationGeo = new GeoCoordinate(Convert.ToDouble(data.ElementAt(0).lat), Convert.ToDouble(data.ElementAt(0).lng));
return destinationGeo;
}

Related

Why first click event of button not working

I have app(net4.7.2) like this:
Program is simple, when user presses OK, im sending request to steam market to get informations about item which user entered (item steam market url) to textbox.
But when im trying to send request, first click event of button not working:
private void btnOK_Click(object sender, EventArgs e)
{
if (txtItemURL.Text.StartsWith("https://steamcommunity.com/market/listings/730/") == true)
{
Helpers.Helper.BuildURL(txtItemURL.Text);
SteamMarketItem SMI = Helpers.Helper.GetItemDetails();
lblPrice.Text = SMI.LowestPrice.ToString() + "$";
pbItemImage.ImageLocation = SMI.ImagePath;
Helpers.Helper.Kontrollar_BerpaEt();
}
else
{
Helpers.Helper.Kontrollar_SifirlaYanlisDaxilEdilib();
}
}
Method GetItemDetails():
public static SteamMarketItem GetItemDetails()
{
WinForms.Control.CheckForIllegalCrossThreadCalls = false;
Task.Run(() =>
{
try
{
using (HttpClient client = new HttpClient())
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
/* Get item info: */
var ResultFromEndpoint1 = client.GetAsync(ReadyEndpointURL1).Result;
var Json1 = ResultFromEndpoint1.Content.ReadAsStringAsync().Result;
dynamic item = serializer.Deserialize<object>(Json1);
marketItem.LowestPrice = float.Parse(((string)item["lowest_price"]).Replace("$", "").Replace(".", ","));
/* Get item image: */
var ResultFromEndpoint2 = client.GetAsync(ReadyEndPointURL2).Result;
var Json2 = ResultFromEndpoint2.Content.ReadAsStringAsync().Result;
var html = ((dynamic)serializer.Deserialize<object>(Json2))["results_html"];
HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(html);
marketItem.ImagePath = htmlDoc.DocumentNode.SelectSingleNode("//img[#class='market_listing_item_img']").Attributes["src"].Value + ".png";
Kontrollar_BerpaEt();
}
}
catch
{
Kontrollar_SifirlaYanlisDaxilEdilib();
}
});
return marketItem;
}
Class SteamMarketItem:
public class SteamMarketItem
{
public string ImagePath { get; set; }
public float LowestPrice { get; set; }
}
When im using Task.Run() first click not working, without Task.Run() working + but main UI thread stopping when request not finished.
I have no idea why this happens, I cant find problem fix myself, I will be glad to get help from you. Thanks.
If you want to use async you need to change your event handler to async so you can use await, please see the following:
1. Change your Event handler to async void, async void is acceptable on event handler methods, you should try to use async Task in place of async void in most other cases, so change your method signature to the following:
private async void btnOK_Click(object sender, EventArgs e)
{
if (txtItemURL.Text.StartsWith("https://steamcommunity.com/market/listings/730/") == true)
{
Helpers.Helper.BuildURL(txtItemURL.Text);
//here we use await to await the task
SteamMarketItem SMI = await Helpers.Helper.GetItemDetails();
lblPrice.Text = SMI.LowestPrice.ToString() + "$";
pbItemImage.ImageLocation = SMI.ImagePath;
Helpers.Helper.Kontrollar_BerpaEt();
}
else
{
Helpers.Helper.Kontrollar_SifirlaYanlisDaxilEdilib();
}
}
2. You shouldn't need to use Task.Run, HttpClient exposes async methods and you can make the method async, also, calling .Result to block on an async method is typically not a good idea and you should make the enclosing method async so you can utilize await:
//Change signature to async and return a Task<T>
public async static Task<SteamMarketItem> GetItemDetails()
{
WinForms.Control.CheckForIllegalCrossThreadCalls = false;
//what is marketItem?? Where is it declared?
try
{
using (HttpClient client = new HttpClient())
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
/* Get item info: */
var ResultFromEndpoint1 = await client.GetAsync(ReadyEndpointURL1);
var Json1 = await ResultFromEndpoint1.Content.ReadAsStringAsync();
dynamic item = serializer.Deserialize<object>(Json1);
marketItem.LowestPrice = float.Parse(((string)item["lowest_price"]).Replace("$", "").Replace(".", ","));
/* Get item image: */
var ResultFromEndpoint2 = await client.GetAsync(ReadyEndPointURL2);
var Json2 = await ResultFromEndpoint2.Content.ReadAsStringAsync();
var html = ((dynamic)serializer.Deserialize<object>(Json2))["results_html"];
HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(html);
marketItem.ImagePath = htmlDoc.DocumentNode.SelectSingleNode("//img[#class='market_listing_item_img']").Attributes["src"].Value + ".png";
Kontrollar_BerpaEt();
}
}
catch
{
Kontrollar_SifirlaYanlisDaxilEdilib();
}
//what is marketItem?? Where is it declared?
return marketItem;
}

how to wait for webclient OpenReadAsync to complete

I am using WebClient to download some stuff from internet in Windows Phone 8.1 app.
Below is the sample code i am using in my app - where i am calling below method, but my webclient is not waiting to complete the read operation and returning immediately after OpenReadAsync call.
how can i make sure that my method return operation must wait till OpenReadCompleted event is completed?
I have seen multiple similar questions, but couldn't find a solution.
MyCustomObject externalObj; // my custom object
private static void CheckNetworkFile()
{
try
{
WebClient webClient = new WebClient();
webClient.OpenReadCompleted += (s, e) =>
{
externalObj = myReadWebclientResponse(e.Result); // my custom method to read the response
};
webClient.OpenReadAsync(new Uri("http://externalURl.com/sample.xml", UriKind.Absolute));
}
catch (Exception)
{
externalObj = null;
}
}
I would advise you to use WebClient.OpenReadTaskAsync with a combination of the async/await keywords introduced in .NET 4.5 instead. You need to add the async keyword to your method, make it return a Task and it is advisable to end your method with the Async postfix:
MyCustomObject externalObj;
private static async Task CheckNetworkFileAsync()
{
try
{
WebClient webClient = new WebClient();
Stream stream = await webClient.OpenReadTaskAsync(new Uri("http://externalURl.com/sample.xml", UriKind.Absolute));
externalObj = myReadWebclientResponse(stream);
}
catch (Exception)
{
externalObj = null;
}
}
Edit:
As you said, WebClient.OpenReadTaskAsync isn't available for WP8.1, So lets create an Extension Method so it will be:
public static class WebClientExtensions
{
public static Task<Stream> OpenReadTaskAsync(this WebClient client, Uri uri)
{
var tcs = new TaskCompletionSource<Stream>();
OpenReadCompletedEventHandler openReadEventHandler = null;
openReadEventHandler = (sender, args) =>
{
try
{
tcs.SetResult(args.Result);
}
catch (Exception e)
{
tcs.SetException(e);
}
};
client.OpenReadCompleted += openReadEventHandler;
client.OpenReadAsync(uri);
return tcs.Task;
}
}
Now you can use it on your WebClient.
You can find great reading material in the async-await wiki and by simply filtering by that tag in the search bar.
I hope this is not too off topic, but others who are researching this might like to know that the above code sample can also be used for WCF calls in Silverlight. Be sure to add the Microsoft.Bcl.Async NuGet package first. Here is a WCF code example:
public static async Task<string> AuthenticateAsync(string userName, string password)
{
var dataAccessServiceClient = new DataAccessServiceClient("DataAccessService");
var taskCompletionSource = new TaskCompletionSource<string>();
EventHandler<AuthenticateCompletedEventArgs> completedHandler = null;
completedHandler = (s, args) =>
{
try
{
taskCompletionSource.SetResult(args.Result);
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
};
dataAccessServiceClient.AuthenticateCompleted += completedHandler;
dataAccessServiceClient.AuthenticateAsync(userName, password);
return await taskCompletionSource.Task;
}
It can be called like this:
var result = await AuthenticateAsync(username, password);

cancel GetAsync request from outside of method

I have large numbers of async requests. At some point, when application is deactivated (paused) I need cancel all requests. I'm looking for a solution to cancel requests outside of async method. can someone point me in a right direction?
Here is chunks of code.
The async method:
public async void GetDetailsAsync(string url)
{
if (this.LastDate == null)
{
this.IsDetailsLoaded = "visible";
NotifyPropertyChanged("IsDetailsLoaded");
Uri uri = new Uri(url);
HttpClient client = new HttpClient();
HtmlDocument htmlDocument = new HtmlDocument();
HtmlNode htmlNode = new HtmlNode(0, htmlDocument, 1);
MovieData Item = new MovieData();
string HtmlResult;
try
{
HtmlRequest = await client.GetAsync(uri);
HtmlResult = await HtmlRequest.Content.ReadAsStringAsync();
}
...
calling method:
for (int i = 0; i < App.ViewModel.Today.Items.Count; i++)
{
App.ViewModel.Today.Items[i].GetDetailsAsync(App.ViewModel.Today.Items[i].DetailsUrl);
}
deactivate event:
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
//Here i need to stop all requests.
}
You just create a single (shared) instance of CancellationTokenSource:
private CancellationTokenSource _cts = new CancellationTokenSource();
Then, tie all asynchronous operations into that token:
public async void GetDetailsAsync(string url)
{
...
HtmlRequest = await client.GetAsync(uri, _cts.Token);
...
}
Finally, cancel the CTS at the appropriate time:
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
_cts.Cancel();
_cts = new CancellationTokenSource();
}

Populate and return entities from DownloadStringCompleted handler in Windows Phone app

I am having a method which fetches HTML from a url, extracts entities by parsing it, and returns List of entites. Here is sample code:
public List<Entity> FetchEntities()
{
List<Entity> myList = new List<Entity>();
string url = "<myUrl>";
string response = String.Empty;
client = new WebClient();
client.DownloadStringCompleted += (sender, e) =>
{
response = e.Result;
// parse response
// extract content and generate entities
// <---- I am currently filling list here
};
client.DownloadStringAsync(new Uri(url));
return myList;
}
The problem is while async call is in progress control returns with empty myList. How can I prevent this. My ultimate goal is to return filled list.
And also this method is in a seperate class library project and being called from windows phone application and I have to keep it like that only. Is there any way to do this or I am missing something? Any help will be greatly appreciated.
You can either pass callback to the method like this and make it async without Tasks, so u have to update method usage slightly.
public void FetchEntities(
Action<List<Entity>> resultCallback,
Action<string> errorCallback)
{
List<Entity> myList = new List<Entity>();
string url = "<myUrl>";
string response = String.Empty;
client = new WebClient();
client.DownloadStringCompleted += (sender, e) =>
{
response = e.Result;
// parse response
// extract content and generate entities
// <---- I am currently filling list here
if (response == null)
{
if (errorCallback != null)
errorCallback("Ooops, something bad happened");
}
else
{
if (callback != null)
callback(myList);
}
};
client.DownloadStringAsync(new Uri(url));
}
The other option is to force it be synchronous. Like that
public List<Entity> FetchEntities()
{
List<Entity> myList = new List<Entity>();
string url = "<myUrl>";
string response = String.Empty;
client = new WebClient();
AutoResetEvent waitHandle = new AutoResetEvent(false);
client.DownloadStringCompleted += (sender, e) =>
{
response = e.Result;
// parse response
// extract content and generate entities
// <---- I am currently filling list here
waitHandle.Set();
};
client.DownloadStringAsync(new Uri(url));
waitHandle.WaitOne();
return myList;
}
That is the point of asynchronous programming to be non-blocking. You can pass a callback as a parameter and handle the result somewhere else instead of trying to return it.
If you need to return the result you can use this TPL library, I've been using it without problem for a while now.
public Task<string> GetWebResultAsync(string url)
{
var tcs = new TaskCompletionSource<string>();
var client = new WebClient();
DownloadStringCompletedEventHandler h = null;
h = (sender, args) =>
{
if (args.Cancelled)
{
tcs.SetCanceled();
}
else if (args.Error != null)
{
tcs.SetException(args.Error);
}
else
{
tcs.SetResult(args.Result);
}
client.DownloadStringCompleted -= h;
};
client.DownloadStringCompleted += h;
client.DownloadStringAsync(new Uri(url));
return tcs.Task;
}
}
And calling it is exactly how you use TPL in .net 4.0
GetWebResultAsnyc(url).ContinueWith((t) =>
{
t.Result //this is the downloaded string
});
or:
var downloadTask = GetWebResultAsync(url);
downloadTask.Wait();
var result = downloadTask.Result; //this is the downloaded string
Hope this helps :)

Silverlight: How to pass data from the request to the response using Webclient Asynchronous mode?

How to access VIP in the proxy_OpenReadCompleted method?
void method1()
{
String VIP = "test";
WebClient proxy = new WebClient();
proxy.OpenReadCompleted += new OpenReadCompletedEventHandler(proxy_OpenReadCompleted);
String urlStr = "someurl/lookup?q=" + keyEntityName + "&fme=1&edo=1&edi=1";
}
void proxy_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
}
There are two approaches to this. First is to pass the string as the second parameter in the OpenReadAsync call, this parameter becomes the value of the UserState property of the event args.
void method1()
{
String VIP = "test";
WebClient proxy = new WebClient();
proxy.OpenReadCompleted += proxy_OpenReadCompleted;
String urlStr = "someurl/lookup?q=" + keyEntityName + "&fme=1&edo=1&edi=1";
proxy.OpenReadAsync(new Uri(urlStr, UriKind.Relative), VIP);
}
void proxy_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
String VIP = (string)e.UserState;
// Do stuff that uses VIP.
}
Another approach is to access the variable directly using a closure:-
void method1()
{
String VIP = "test";
WebClient proxy = new WebClient();
proxy.OpenReadCompleted += (s, args) =>
{
// Do stuff that uses VIP.
}
String urlStr = "someurl/lookup?q=" + keyEntityName + "&fme=1&edo=1&edi=1";
proxy.OpenReadAsync(new Uri(urlStr, UriKind.Relative), VIP);
}
void method1()
{
String VIP = "test";
WebClient proxy = new WebClient();
proxy.OpenReadCompleted += (s,e) => proxy_OpenReadCompleted(s,e,VIP);
String urlStr = "someurl/lookup?q=" + keyEntityName + "&fme=1&edo=1&edi=1";
}
Be aware that if the async callback method writes into a databound variable, you are likely to get a cross-thread exception. You will need to use BeginInvoke() to get back to the UI thread. Here's an example using WCF services, but the principle is the same.
public void examsCallback(IAsyncResult result)
{
try
{
EntityList<ExamEntity> examList = ((IExamService) result.AsyncState).EndGetAllExams(result);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
foreach (ExamEntity exam in examList)
{
Exams.Add(exam);
}
ItemCount = Exams.Count;
TotalItemCount = Exams.ItemCount;
});
}
catch (Exception ex)
{
ErrorHandler.Handle(ex);
}
}

Categories