I'm wondering if there is a way to report WebClient progress without using EAP(Event-based Asynchronous Pattern).
Old way(using EAP) would be:
var client = new WebClient();
client.DownloadProgressChanged += (s,e) => { //progress reporting }
client.DownloadFileCompleted += (s,e) => { Console.Write("download finished" }
client.DownloadFileAsync(file);
With async/await this can be written as:
var client = new WebClient();
client.DownloadProgressChanged += (s,e) => { //progress reporting }
await client.DownloadFileTaskAsync(file);
Console.Write("downlaod finished");
But in the second example i'm using both EAP and TAP(Task-based Asynchronous Pattern).
Isn't mixing two patterns of asynchrony considered as a bad practice?
Is there a way to achieve the same without using EAP?
I have read about IProgress interface, but I think there is no way to use it to report WebClient progress.
The bad news is that the answer is NO!
The good news is that any EAP API can be converted into a TAP API.
Try this:
public static class WebClientExtensios
{
public static async Task DownloadFileTaskAsync(
this WebClient webClient,
Uri address,
string fileName,
IProgress<Tuple<long, int, long>> progress)
{
// Create the task to be returned
var tcs = new TaskCompletionSource<object>(address);
// Setup the callback event handler handlers
AsyncCompletedEventHandler completedHandler = (cs, ce) =>
{
if (ce.UserState == tcs)
{
if (ce.Error != null) tcs.TrySetException(ce.Error);
else if (ce.Cancelled) tcs.TrySetCanceled();
else tcs.TrySetResult(null);
}
};
DownloadProgressChangedEventHandler progressChangedHandler = (ps, pe) =>
{
if (pe.UserState == tcs)
{
progress.Report(
Tuple.Create(
pe.BytesReceived,
pe.ProgressPercentage,
pe.TotalBytesToReceive));
}
};
try
{
webClient.DownloadFileCompleted += completedHandler;
webClient.DownloadProgressChanged += progressChangedHandler;
webClient.DownloadFileAsync(address, fileName, tcs);
await tcs.Task;
}
finally
{
webClient.DownloadFileCompleted -= completedHandler;
webClient.DownloadProgressChanged -= progressChangedHandler;
}
}
}
And just use it like this:
void Main()
{
var webClient = new WebClient();
webClient.DownloadFileTaskAsync(
new Uri("http://feeds.paulomorgado.net/paulomorgado/blogs/en"),
#"c:\temp\feed.xml",
new Progress<Tuple<long, int, long>>(t =>
{
Console.WriteLine($#"
Bytes received: {t.Item1,25:#,###}
Progress percentage: {t.Item2,25:#,###}
Total bytes to receive: {t.Item3,25:#,###}");
})).Wait();
}
Related
So I thought Webclient.DownloadFileAysnc would have a default timeout but looking around the documentation I cannot find anything about it anywhere so I'm guessing it doesn't.
I am trying to download a file from the internet like so:
using (WebClient wc = new WebClient())
{
wc.DownloadProgressChanged += ((sender, args) =>
{
IndividualProgress = args.ProgressPercentage;
});
wc.DownloadFileCompleted += ((sender, args) =>
{
if (args.Error == null)
{
if (!args.Cancelled)
{
File.Move(filePath, Path.ChangeExtension(filePath, ".jpg"));
}
mr.Set();
}
else
{
ex = args.Error;
mr.Set();
}
});
wc.DownloadFileAsync(new Uri("MyInternetFile", filePath);
mr.WaitOne();
if (ex != null)
{
throw ex;
}
}
But if I turn off my WiFi (simulating a drop of internet connection) my application just pauses and the download stops but it will never report that through to the DownloadFileCompleted method.
For this reason I would like to implement a timeout on my WebClient.DownloadFileAsync method. Is this possible?
As an aside I am using .Net 4 and don't want to add references to third party libraries so cannot use the Async/Await keywords
You can use WebClient.DownloadFileAsync(). Now inside a timer you can call CancelAsync() like so:
System.Timers.Timer aTimer = new System.Timers.Timer();
System.Timers.ElapsedEventHandler handler = null;
handler = ((sender, args)
=>
{
aTimer.Elapsed -= handler;
wc.CancelAsync();
});
aTimer.Elapsed += handler;
aTimer.Interval = 100000;
aTimer.Enabled = true;
Else create your own weclient
public class NewWebClient : WebClient
{
protected override WebRequest GetWebRequest(Uri address)
{
var req = base.GetWebRequest(address);
req.Timeout = 18000;
return req;
}
}
Create a WebClientAsync class that implements the timer in the constructor. This way you aren't copying and pasting the timer code into every implementation.
public class WebClientAsync : WebClient
{
private int _timeoutMilliseconds;
public EdmapWebClientAsync(int timeoutSeconds)
{
_timeoutMilliseconds = timeoutSeconds * 1000;
Timer timer = new Timer(_timeoutMilliseconds);
ElapsedEventHandler handler = null;
handler = ((sender, args) =>
{
timer.Elapsed -= handler;
this.CancelAsync();
});
timer.Elapsed += handler;
timer.Enabled = true;
}
protected override WebRequest GetWebRequest(Uri address)
{
WebRequest request = base.GetWebRequest(address);
request.Timeout = _timeoutMilliseconds;
((HttpWebRequest)request).ReadWriteTimeout = _timeoutMilliseconds;
return request;
}
protected override voidOnDownloadProgressChanged(DownloadProgressChangedEventArgs e)
{
base.OnDownloadProgressChanged(e);
timer.Reset(); //If this does not work try below
timer.Start();
}
}
This will allow you to timeout if you lose Internet connection while downloading a file.
Here is another implementation, I tried to avoid any shared class/object variables to avoid trouble with multiple calls:
public Task<string> DownloadFile(Uri url)
{
var tcs = new TaskCompletionSource<string>();
Task.Run(async () =>
{
bool hasProgresChanged = false;
var timer = new Timer(new TimeSpan(0, 0, 20).TotalMilliseconds);
var client = new WebClient();
void downloadHandler(object s, DownloadProgressChangedEventArgs e) => hasProgresChanged = true;
void timerHandler(object s, ElapsedEventArgs e)
{
timer.Stop();
if (hasProgresChanged)
{
timer.Start();
hasProgresChanged = false;
}
else
{
CleanResources();
tcs.TrySetException(new TimeoutException("Download timedout"));
}
}
void CleanResources()
{
client.DownloadProgressChanged -= downloadHandler;
client.Dispose();
timer.Elapsed -= timerHandler;
timer.Dispose();
}
string filePath = Path.Combine(Path.GetTempPath(), Path.GetFileName(url.ToString()));
try
{
client.DownloadProgressChanged += downloadHandler;
timer.Elapsed += timerHandler;
timer.Start();
await client.DownloadFileTaskAsync(url, filePath);
}
catch (Exception e)
{
tcs.TrySetException(e);
}
finally
{
CleanResources();
}
return tcs.TrySetResult(filePath);
});
return tcs.Task;
}
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);
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.
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 :)