In one action of my MVC 4 apps, I have a call:
public ActionResult Test()
{
DownloadAsync("uri","file path");
return Content("OK");
}
DownloadAsync return a Task and I expect to see the DownloadAsync run in background. But I always see that MVC only response when the Task of DownloadAsync is completed (means that need wait for download complete before response). If I wrap the async call in to Task.Run() or Task.Factory.StartNew(), then it works as my expectation. Here's method DownloadAsync:
private Task DownloadAsync(string url, string originalFile)
{
var tsc = new TaskCompletionSource<bool>();
var client = new WebClient();
AsyncCompletedEventHandler completedHandler = null;
completedHandler = (s, e) =>
{
var wc = (WebClient)s;
wc.DownloadFileCompleted -= completedHandler;
if (e.Cancelled)
{
tsc.TrySetCanceled();
}
else if (e.Error != null)
{
tsc.TrySetException(e.Error);
}
else
{
tsc.SetResult(true);
}
wc.Dispose();
};
client.DownloadFileCompleted += completedHandler;
client.DownloadFileAsync(new Uri(url), originalFile);
return tsc.Task;
}
So my question are:
Why MVC request need wait for complete Task in this case? Is there any special for Task created by TaskCompletionSource<T>?
How to make the Task of DownloadAsync run in background without pause the response of MVC request?
Thanks,
Related
I am getting lots of delay when saving data in database. I have one exe (Deskptop Application) which reads data from serial port and push that entry in to database through web API service but my application get hangs on this line:
httpClient.PostAsync("api/MyController/Save", httpConent).Result;
This exe is responsible to call my web API service method and save data to my database.
This is my code:
void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int dataLength = _serialPort.BytesToRead;
byte[] data = new byte[dataLength];
int nbrDataRead = _serialPort.Read(data, 0, dataLength);
if (nbrDataRead == 0)
return;
// Send data to whom ever interested
if (NewSerialDataRecieved != null)
{
NewSerialDataRecieved(this, new SerialDataEventArgs(data));
}
}
void _spManager_NewSerialDataRecieved(object sender, SerialDataEventArgs e)
{
if (this.InvokeRequired)
{
// Using this.Invoke causes deadlock when closing serial port, and BeginInvoke is good practice anyway.
//// Fired-off asynchronously; let the current thread continue.
this.BeginInvoke(new EventHandler<SerialDataEventArgs>(_spManager_NewSerialDataRecieved), new object[] { sender, e });
return;
}
//data is converted to text
string str = Encoding.ASCII.GetString(e.Data);
if (!string.IsNullOrEmpty(str))
{
CallWebservice(str)
}
}
public void CallWebservice(string xmlRequest)
{
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri("WebService Url");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
StringContent httpConent = new StringContent(xmlRequest, Encoding.UTF8);
HttpResponseMessage responseMessage = null;
try
{
responseMessage = httpClient.PostAsync("api/MyController/Save", httpConent).Result;
}
catch (Exception ex)
{
if (responseMessage == null)
{
responseMessage = new HttpResponseMessage();
}
responseMessage.StatusCode = HttpStatusCode.InternalServerError;
responseMessage.ReasonPhrase = string.Format("RestHttpClient.SendRequest failed: {0}", ex);
}
}
}
My web api method:
public async Task<HttpResponseMessage> Save(HttpRequestMessage request)
{
var requestdata = request.Content.ReadAsStringAsync().Result;//extract Users Id's from this
var users=context.User.Where(t => (t.Stats == userId1) || (t.Stats == userId2)).ToList();
var objUsersMapping= new UsersMapping();
objUsersMapping.Work1 = users[0].Work1;
objUsersMapping.Work2 = users[1].Work1;
await this.SaveUsersMapping(objUsersMapping);
}
public async Task<UsersMapping> SaveUsersMapping(UsersMapping objUsersMapping)
{
using (var context = new MyEntities())
{
try
{
context.UsersMapping.Add(objUsersMapping);
await context.SaveChangesAsync();
return objUsersMapping;
}
catch (Exception foExe)
{
return null;
}
}
}
I haven't work much on Windows application so I am not understanding why my application is hanging.
Note: data will be continuously coming to my serial port so saving data through web service should not disturb _serialPort_DataReceived event.
This is a summary of my comments beneath the OP's question
You are calling an asynchronous method synchronously. That will cause the current thread to block. Get rid of the .Result and alter the rest of the code accordingly (like including async and await there too).
e.g. change this line
responseMessage = httpClient.PostAsync("api/MyController/Save", httpConent).Result;
...to:
responseMessage = await httpClient.PostAsync("api/MyController/Save", httpConent);
Your method signature will need to be changed as follows:
public async Task CallWebservice(string xmlRequest)
{
}
Any method that calls it will also need to be async and use await for example your _spManager_NewSerialDataRecieved() method.
Note it has been changed from void to async void. Note too the await prior to CallWebservice().
async void _spManager_NewSerialDataRecieved(object sender, SerialDataEventArgs e)
{
if (this.InvokeRequired)
{
// Using this.Invoke causes deadlock when closing serial port, and BeginInvoke is good practice anyway.
//// Fired-off asynchronously; let the current thread continue.
this.BeginInvoke(new EventHandler<SerialDataEventArgs>(_spManager_NewSerialDataRecieved), new object[] { sender, e });
return;
}
//data is converted to text
string str = Encoding.ASCII.GetString(e.Data);
if (!string.IsNullOrEmpty(str))
{
await CallWebservice(str)
}
}
A note on async void
Because the above method is an event handler it is fine for the method to be async void. Generally you want to avoid async void in non event handler code. For more info see this brilliant article by Mr Stephen Cleary.
Is this the only problem sir??
You should fix your async Save() method on the server too as it also has a .Result(). That will block the current thread on the server. Prefix it with a await. Generally you want to avoid .Result as a means to wait for the task to complete. It is safe to use as a means to obtain the result after you have awaited it, but there are more elegant ways to await and get the result in a single line of code. e.g. x = await FooAsync();.
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);
When new users register on my site I require them to verify their email by sending them an email with a unique link in it.
If I do this synchronously with the Controller Register Action it takes about 3 - 5 seconds for the page to return as the email method takes some time to complete.
In order to deal with this I am doing this:
Thread emailRequestThread = new Thread(() => (new EmailSender()).SendConfirmAdressEmail(user));
emailRequestThread.Start();
It is working but is this a bad idea?
If so how should I accomplish the same result?
Instead of spinning up a new Thread to send mail, i would go with the async approach.
What we do here is wrap the StmpClient.SendAsync EAP pattern with a Task so we can await using its TaskAwaitable:
public static Task SendAsyncTask(this SmtpClient client, MailMessage message)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
Guid sendGuid = Guid.NewGuid();
SendCompletedEventHandler handler = null;
handler = (o, ea) =>
{
if (ea.UserState is Guid && ((Guid)ea.UserState) == sendGuid)
{
client.SendCompleted -= handler;
if (ea.Cancelled)
{
tcs.SetCanceled();
}
else if (ea.Error != null)
{
tcs.SetException(ea.Error);
}
else
{
tcs.SetResult(null);
}
}
};
client.SendCompleted += handler;
client.SendAsync(message, sendGuid);
return tcs.Task;
}
And then use it this way:
Task sendTask = await client.SendAsyncTask(message);
I am using this method to instantiate a web browser programmatically, navigate to a url and return a result when the document has completed.
How would I be able to stop the Task and have GetFinalUrl() return null if the document takes more than 5 seconds to load?
I have seen many examples using a TaskFactory but I haven't been able to apply it to this code.
private Uri GetFinalUrl(PortalMerchant portalMerchant)
{
SetBrowserFeatureControl();
Uri finalUri = null;
if (string.IsNullOrEmpty(portalMerchant.Url))
{
return null;
}
Uri trackingUrl = new Uri(portalMerchant.Url);
var task = MessageLoopWorker.Run(DoWorkAsync, trackingUrl);
task.Wait();
if (!String.IsNullOrEmpty(task.Result.ToString()))
{
return new Uri(task.Result.ToString());
}
else
{
throw new Exception("Parsing Failed");
}
}
// by Noseratio - http://stackoverflow.com/users/1768303/noseratio
static async Task<object> DoWorkAsync(object[] args)
{
_threadCount++;
Console.WriteLine("Thread count:" + _threadCount);
Uri retVal = null;
var wb = new WebBrowser();
wb.ScriptErrorsSuppressed = true;
TaskCompletionSource<bool> tcs = null;
WebBrowserDocumentCompletedEventHandler documentCompletedHandler = (s, e) => tcs.TrySetResult(true);
foreach (var url in args)
{
tcs = new TaskCompletionSource<bool>();
wb.DocumentCompleted += documentCompletedHandler;
try
{
wb.Navigate(url.ToString());
await tcs.Task;
}
finally
{
wb.DocumentCompleted -= documentCompletedHandler;
}
retVal = wb.Url;
wb.Dispose();
return retVal;
}
return null;
}
public static class MessageLoopWorker
{
#region Public static methods
public static async Task<object> Run(Func<object[], Task<object>> worker, params object[] args)
{
var tcs = new TaskCompletionSource<object>();
var thread = new Thread(() =>
{
EventHandler idleHandler = null;
idleHandler = async (s, e) =>
{
// handle Application.Idle just once
Application.Idle -= idleHandler;
// return to the message loop
await Task.Yield();
// and continue asynchronously
// propogate the result or exception
try
{
var result = await worker(args);
tcs.SetResult(result);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
// signal to exit the message loop
// Application.Run will exit at this point
Application.ExitThread();
};
// handle Application.Idle just once
// to make sure we're inside the message loop
// and SynchronizationContext has been correctly installed
Application.Idle += idleHandler;
Application.Run();
});
// set STA model for the new thread
thread.SetApartmentState(ApartmentState.STA);
// start the thread and await for the task
thread.Start();
try
{
return await tcs.Task;
}
finally
{
thread.Join();
}
}
#endregion
}
Updated: the latest version of the WebBrowser-based console web scraper can be found on Github.
Updated: Adding a pool of WebBrowser objects for multiple parallel downloads.
Do you have an example of how to do this in a console app by any
chance? Also I don't think webBrowser can be a class variable because
I am running the whole thing in a parallell for each, iterating
thousands of URLs
Below is an implementation of more or less generic **WebBrowser-based web scraper **, which works as console application. It's a consolidation of some of my previous WebBrowser-related efforts, including the code referenced in the question:
Capturing an image of the web page with opacity
Loading a page with dynamic AJAX content
Creating an STA message loop thread for WebBrowser
Loading a set of URLs, one after another
Printing a set of URLs with WebBrowser
Web page UI automation
A few points:
Reusable MessageLoopApartment class is used to start and run a WinForms STA thread with its own message pump. It can be used from a console application, as below. This class exposes a TPL Task Scheduler (FromCurrentSynchronizationContext) and a set of Task.Factory.StartNew wrappers to use this task scheduler.
This makes async/await a great tool for running WebBrowser navigation tasks on that separate STA thread. This way, a WebBrowser object gets created, navigated and destroyed on that thread. Although, MessageLoopApartment is not tied up to WebBrowser specifically.
It's important to enable HTML5 rendering using Browser Feature
Control, as otherwise the WebBrowser obejcts runs in IE7 emulation mode by default.
That's what SetFeatureBrowserEmulation does below.
It may not always be possible to determine when a web page has finished rendering with 100% probability. Some pages are quite complex and use continuous AJAX updates. Yet we
can get quite close, by handling DocumentCompleted event first, then polling the page's current HTML snapshot for changes and checking the WebBrowser.IsBusy property. That's what NavigateAsync does below.
A time-out logic is present on top of the above, in case the page rendering is never-ending (note CancellationTokenSource and CreateLinkedTokenSource).
using Microsoft.Win32;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Console_22239357
{
class Program
{
// by Noseratio - https://stackoverflow.com/a/22262976/1768303
// main logic
static async Task ScrapeSitesAsync(string[] urls, CancellationToken token)
{
using (var apartment = new MessageLoopApartment())
{
// create WebBrowser inside MessageLoopApartment
var webBrowser = apartment.Invoke(() => new WebBrowser());
try
{
foreach (var url in urls)
{
Console.WriteLine("URL:\n" + url);
// cancel in 30s or when the main token is signalled
var navigationCts = CancellationTokenSource.CreateLinkedTokenSource(token);
navigationCts.CancelAfter((int)TimeSpan.FromSeconds(30).TotalMilliseconds);
var navigationToken = navigationCts.Token;
// run the navigation task inside MessageLoopApartment
string html = await apartment.Run(() =>
webBrowser.NavigateAsync(url, navigationToken), navigationToken);
Console.WriteLine("HTML:\n" + html);
}
}
finally
{
// dispose of WebBrowser inside MessageLoopApartment
apartment.Invoke(() => webBrowser.Dispose());
}
}
}
// entry point
static void Main(string[] args)
{
try
{
WebBrowserExt.SetFeatureBrowserEmulation(); // enable HTML5
var cts = new CancellationTokenSource((int)TimeSpan.FromMinutes(3).TotalMilliseconds);
var task = ScrapeSitesAsync(
new[] { "http://example.com", "http://example.org", "http://example.net" },
cts.Token);
task.Wait();
Console.WriteLine("Press Enter to exit...");
Console.ReadLine();
}
catch (Exception ex)
{
while (ex is AggregateException && ex.InnerException != null)
ex = ex.InnerException;
Console.WriteLine(ex.Message);
Environment.Exit(-1);
}
}
}
/// <summary>
/// WebBrowserExt - WebBrowser extensions
/// by Noseratio - https://stackoverflow.com/a/22262976/1768303
/// </summary>
public static class WebBrowserExt
{
const int POLL_DELAY = 500;
// navigate and download
public static async Task<string> NavigateAsync(this WebBrowser webBrowser, string url, CancellationToken token)
{
// navigate and await DocumentCompleted
var tcs = new TaskCompletionSource<bool>();
WebBrowserDocumentCompletedEventHandler handler = (s, arg) =>
tcs.TrySetResult(true);
using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: true))
{
webBrowser.DocumentCompleted += handler;
try
{
webBrowser.Navigate(url);
await tcs.Task; // wait for DocumentCompleted
}
finally
{
webBrowser.DocumentCompleted -= handler;
}
}
// get the root element
var documentElement = webBrowser.Document.GetElementsByTagName("html")[0];
// poll the current HTML for changes asynchronosly
var html = documentElement.OuterHtml;
while (true)
{
// wait asynchronously, this will throw if cancellation requested
await Task.Delay(POLL_DELAY, token);
// continue polling if the WebBrowser is still busy
if (webBrowser.IsBusy)
continue;
var htmlNow = documentElement.OuterHtml;
if (html == htmlNow)
break; // no changes detected, end the poll loop
html = htmlNow;
}
// consider the page fully rendered
token.ThrowIfCancellationRequested();
return html;
}
// enable HTML5 (assuming we're running IE10+)
// more info: https://stackoverflow.com/a/18333982/1768303
public static void SetFeatureBrowserEmulation()
{
if (System.ComponentModel.LicenseManager.UsageMode != System.ComponentModel.LicenseUsageMode.Runtime)
return;
var appName = System.IO.Path.GetFileName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
Registry.SetValue(#"HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION",
appName, 10000, RegistryValueKind.DWord);
}
}
/// <summary>
/// MessageLoopApartment
/// STA thread with message pump for serial execution of tasks
/// by Noseratio - https://stackoverflow.com/a/22262976/1768303
/// </summary>
public class MessageLoopApartment : IDisposable
{
Thread _thread; // the STA thread
TaskScheduler _taskScheduler; // the STA thread's task scheduler
public TaskScheduler TaskScheduler { get { return _taskScheduler; } }
/// <summary>MessageLoopApartment constructor</summary>
public MessageLoopApartment()
{
var tcs = new TaskCompletionSource<TaskScheduler>();
// start an STA thread and gets a task scheduler
_thread = new Thread(startArg =>
{
EventHandler idleHandler = null;
idleHandler = (s, e) =>
{
// handle Application.Idle just once
Application.Idle -= idleHandler;
// return the task scheduler
tcs.SetResult(TaskScheduler.FromCurrentSynchronizationContext());
};
// handle Application.Idle just once
// to make sure we're inside the message loop
// and SynchronizationContext has been correctly installed
Application.Idle += idleHandler;
Application.Run();
});
_thread.SetApartmentState(ApartmentState.STA);
_thread.IsBackground = true;
_thread.Start();
_taskScheduler = tcs.Task.Result;
}
/// <summary>shutdown the STA thread</summary>
public void Dispose()
{
if (_taskScheduler != null)
{
var taskScheduler = _taskScheduler;
_taskScheduler = null;
// execute Application.ExitThread() on the STA thread
Task.Factory.StartNew(
() => Application.ExitThread(),
CancellationToken.None,
TaskCreationOptions.None,
taskScheduler).Wait();
_thread.Join();
_thread = null;
}
}
/// <summary>Task.Factory.StartNew wrappers</summary>
public void Invoke(Action action)
{
Task.Factory.StartNew(action,
CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Wait();
}
public TResult Invoke<TResult>(Func<TResult> action)
{
return Task.Factory.StartNew(action,
CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Result;
}
public Task Run(Action action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler);
}
public Task<TResult> Run<TResult>(Func<TResult> action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler);
}
public Task Run(Func<Task> action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler).Unwrap();
}
public Task<TResult> Run<TResult>(Func<Task<TResult>> action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler).Unwrap();
}
}
}
I suspect running a processing loop on another thread will not work out well, since WebBrowser is a UI component that hosts an ActiveX control.
When you're writing TAP over EAP wrappers, I recommend using extension methods to keep the code clean:
public static Task<string> NavigateAsync(this WebBrowser #this, string url)
{
var tcs = new TaskCompletionSource<string>();
WebBrowserDocumentCompletedEventHandler subscription = null;
subscription = (_, args) =>
{
#this.DocumentCompleted -= subscription;
tcs.TrySetResult(args.Url.ToString());
};
#this.DocumentCompleted += subscription;
#this.Navigate(url);
return tcs.Task;
}
Now your code can easily apply a timeout:
async Task<string> GetUrlAsync(string url)
{
using (var wb = new WebBrowser())
{
var navigate = wb.NavigateAsync(url);
var timeout = Task.Delay(TimeSpan.FromSeconds(5));
var completed = await Task.WhenAny(navigate, timeout);
if (completed == navigate)
return await navigate;
return null;
}
}
which can be consumed as such:
private async Task<Uri> GetFinalUrlAsync(PortalMerchant portalMerchant)
{
SetBrowserFeatureControl();
if (string.IsNullOrEmpty(portalMerchant.Url))
return null;
var result = await GetUrlAsync(portalMerchant.Url);
if (!String.IsNullOrEmpty(result))
return new Uri(result);
throw new Exception("Parsing Failed");
}
I'm trying to take benefit from Noseratio's solution as well as following advices from Stephen Cleary.
Here is the code I updated to include in the code from Stephen the code from Noseratio regarding the AJAX tip.
First part: the Task NavigateAsync advised by Stephen
public static Task<string> NavigateAsync(this WebBrowser #this, string url)
{
var tcs = new TaskCompletionSource<string>();
WebBrowserDocumentCompletedEventHandler subscription = null;
subscription = (_, args) =>
{
#this.DocumentCompleted -= subscription;
tcs.TrySetResult(args.Url.ToString());
};
#this.DocumentCompleted += subscription;
#this.Navigate(url);
return tcs.Task;
}
Second part: a new Task NavAjaxAsync to run the tip for AJAX (based on Noseratio's code)
public static async Task<string> NavAjaxAsync(this WebBrowser #this)
{
// get the root element
var documentElement = #this.Document.GetElementsByTagName("html")[0];
// poll the current HTML for changes asynchronosly
var html = documentElement.OuterHtml;
while (true)
{
// wait asynchronously
await Task.Delay(POLL_DELAY);
// continue polling if the WebBrowser is still busy
if (webBrowser.IsBusy)
continue;
var htmlNow = documentElement.OuterHtml;
if (html == htmlNow)
break; // no changes detected, end the poll loop
html = htmlNow;
}
return #this.Document.Url.ToString();
}
Third part: a new Task NavAndAjaxAsync to get the navigation and the AJAX
public static async Task NavAndAjaxAsync(this WebBrowser #this, string url)
{
await #this.NavigateAsync(url);
await #this.NavAjaxAsync();
}
Fourth and last part: the updated Task GetUrlAsync from Stephen with Noseratio's code for AJAX
async Task<string> GetUrlAsync(string url)
{
using (var wb = new WebBrowser())
{
var navigate = wb.NavAndAjaxAsync(url);
var timeout = Task.Delay(TimeSpan.FromSeconds(5));
var completed = await Task.WhenAny(navigate, timeout);
if (completed == navigate)
return await navigate;
return null;
}
}
I'd like to know if this is the right approach.
take the following code:
public async Task<string> AuthenticatedGetData(string url, string token)
{
WebClient client = new WebClient();
client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(WebClient_DownloadStringCompleted);
client.DownloadStringAsync(new Uri(url + "?oauth_token=" + token));
}
private void WebClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
string response = e.Result;
}
WebClient_DownloadStringCompleted gets called... and response = the response I want... Great. perfect...
Now consider how i'm calling this AuthenticatedGetData method:
It is being called from a kind of repository... The repository wants a string so that it can serialize and do stuff with the resulting object...
So everything is running async fromt he repository... The call gets made to the authenticatedgetdata, it then makes a request... but because the downloadstringasync does not have a .Result() method and because downloadstringcompleted requires a void method to call... I cannot return the result string to the calling repository.
Any ideas on what I must do to get client.DownloadStringAsync to return the response string on completion?
Is it that I just simply have to tightly couple my data access operations to this specific app.. It seems so un..re-usable :( I really want to keep my whole authentication stuff totally separate from what's going to happen. and I do not want to keep repeating the above code for each repository, because it's going to be the same for each anyway!
Edit:://
I've created an abstract method in my class that deals with the above requests... and then I extend this class with my repository and implement the abstract method. sound good?
Edit:// Calling code as requested:
public class OrganisationRepository
{
PostRequest postRequest;
public OrganisationRepository()
{
this.postRequest = new PostRequest();
}
public IEnumerable<Organisation> GetAll()
{
string requestUrl = BlaBla.APIURL + "/org/";
string response = postRequest.AuthenticatedGetData(requestUrl, BlaBla.Contract.AccessToken).Result;
}
}
public class PostRequest
{
public Task<string> AuthenticatedGetData(string url, string token)
{
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
WebClient client = new WebClient();
client.DownloadStringCompleted += (sender, e) =>
{
if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else if (e.Cancelled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(e.Result);
}
};
client.DownloadStringAsync(new Uri(url + "?oauth_token=" + token));
return tcs.Task;
}
}
I'm not sure what the windows-phone-8 limitations are with regards to this. But I think this should work.
public Task<string> AuthenticatedGetData(string url, string token)
{
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
WebClient client = new WebClient();
client.DownloadStringCompleted += (sender, e) =>
{
if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else if (e.Cancelled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(e.Result);
}
};
client.DownloadStringAsync(new Uri(url + "?oauth_token=" + token));
return tcs.Task;
}
You might also be able to get away with this (not sure if it works on windows phone 8)
public Task<string> AuthenticatedGetData(string url, string token)
{
WebClient client = new WebClient();
return client.DownloadStringTaskAsync(new Uri(url + "?oauth_token=" + token));
}
A solution is to not use the WebClient library at all. Here's the issue: you are awaiting your async AuthenticatedGetData Task, but it does nothing because the Task has no await calls in it meaning it will finish instantly. Calling the .result on the task will always be null because you can never return a value in that method. The WebClient relies on calling a function call after the DownloadCompleted event is fired. However, there is no way for anyone to know exactly when this happens, unless they also subscribe to that DownloadCompleted event handler, which is silly. I recommend making a true async web request service using HttpWebRequest http://blogs.msdn.com/b/andy_wigley/archive/2013/02/07/async-and-await-for-http-networking-on-windows-phone.aspx good luck.