I am trying to download a large file(around 1GB) my server.When I start downloading I am unable to use the app till the download completes. It is blocking the UI and its becoming unresponsive.
In below code I am calling DownloadFile method when the user download button on the UI.And then download starts, but the UI is freezed.
I read that DownloadFileAsync won't block the UI. But here its blocking. How to use it in correct way. There are several answers but none is working when I am testing.
Code:
Button call:
private void Button_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("1");
DownloadGamefile DGF = new DownloadGamefile();
Debug.WriteLine("2" + Environment.CurrentDirectory);
DGF.DownloadFile("URL(https link to zip file)", Environment.CurrentDirectory + #"\ABC.zip");
Debug.WriteLine("3");
}
Download code:
class DownloadGamefile
{
private volatile bool _completed;
public void DownloadFile(string address, string location)
{
WebClient client = new WebClient();
Uri Uri = new Uri(address);
_completed = false;
client.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgress);
client.DownloadFileAsync(Uri, location);
}
private void DownloadProgress(object sender, DownloadProgressChangedEventArgs e)
{
// Displays the operation identifier, and the transfer progress.
Console.WriteLine("{0} downloaded {1} of {2} bytes. {3} % complete...",
(string)e.UserState,
e.BytesReceived,
e.TotalBytesToReceive,
e.ProgressPercentage);
}
private void Completed(object sender, AsyncCompletedEventArgs e)
{
if (e.Cancelled == true)
{
Console.WriteLine("Download has been canceled.");
}
else
{
Console.WriteLine("Download completed!");
}
_completed = true;
}
}
Please refer to this link. The actual problem is getting lots of feedback about the number of bytes downloaded(about progress of download process). Take a timer to get progress for every 2 seconds or any time, this solved the problem.
Related
I have created a class that downloads files from a web client and the completed method returns a message in the console of the downloaded yes or no. This works, but is it also possible to read out how big the file is before downloading, and if the download is interrupted and not completely downloaded to delete this file?
How can I add this detection? I was thinking to read out the size of each file via readallbytes to get the size but so far without success
This is my code so far
public class FileDownload
{
private volatile bool _completed;
public bool DownloadCompleted
{
get
{
return _completed;
}
}
public async Task DownloadFile(string address, string location)
{
using (WebClient client = new WebClient())
{
Uri Uri = new Uri(address);
_completed = false;
client.Headers.Add("Authorization", await Header.getAuthorizationHeader());
client.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(Completed);
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgress);
client.DownloadFileAsync(Uri, location);
}
}
private void Completed(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (e.Cancelled == true)
{
Console.WriteLine("Download has been canceled.");
_completed = false;
}
else if (e.Error != null)
{
Console.WriteLine("Download error!");
_completed = false;
}
else
{
Console.WriteLine("Download completed!");
_completed = true;
}
}
private void DownloadProgress(object sender, DownloadProgressChangedEventArgs e)
{
// Displays the operation identifier, and the transfer progress.
Console.WriteLine("{0} downloaded {1} of {2} bytes. {3} % complete...",
(string)e.UserState,
e.BytesReceived,
e.TotalBytesToReceive,
e.ProgressPercentage);
}
}
// override DownloadFileAsync timeout to 10 second
public class WebClientWithTimeout : WebClient
{
//10 secs default
public int Timeout
{
get;
set;
} = 10000;
//the above will not work for async requests :(
//let's create a workaround by hiding the method
//and creating our own version of DownloadStringTaskAsync
public new async Task<string> DownloadStringTaskAsync(Uri address)
{
var t = base.DownloadStringTaskAsync(address);
if (await Task.WhenAny(t, Task.Delay(Timeout)).ConfigureAwait(false) != t) //time out!
{
CancelAsync();
}
return await t.ConfigureAwait(false);
}
//for sync requests
protected override WebRequest GetWebRequest(Uri uri)
{
var w = base.GetWebRequest(uri);
w.Timeout = Timeout; //10 seconds timeout
return w;
}
Instead of having complicated logic, you could download it as another name and only rename it to the correct name after the download completed. If all your temporary files followed a pattern, you could delete those files when you start the program, since they obviously were left over from a prior run that failed.
You may also want to delete the temporary file if you find out the download failed (you already know, sice you are printing it to console).
Im trying to download files using extended WebClient with set timeout and I have a problem with the timeout (or what I think should cause timeout).
When I start the download with WebClient and receive some data, then disconnect wifi - my program hangs on the download without throwing any exception. How can I fix this?
EDIT: It actually throws exception but way later than it should (5 minutes vs 1 second which i set) - that is what Im trying to fix.
If you find anything else wrong with my code, please let me know too. Thank you for help
This is my extended class
class WebClientWithTimeout : WebClient
{
protected override WebRequest GetWebRequest(Uri address)
{
WebRequest w = base.GetWebRequest(address);
w.Timeout = 1000;
return w;
}
}
This is the download
using (WebClientWithTimeout wct = new WebClientWithTimeout())
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
try
{
wct.DownloadFile("https://example.com", file);
}
catch (Exception e)
{
Console.WriteLine("Download: {0} failed with exception:{1} {2}", file, Environment.NewLine, e);
}
}
Try this, you can avoid UI blocking by this. Coming the WiFi when device connects to WiFi the download resumes.
//declare globally
DateTime lastDownloaded = DateTime.Now;
Timer t = new Timer();
WebClient wc = new WebClient();
//declarewherever you initiate download my case button click
private void button1_Click(object sender, EventArgs e)
{
wc.DownloadProgressChanged += Wc_DownloadProgressChanged;
wc.DownloadFileCompleted += Wc_DownloadFileCompleted;
lastDownloaded = DateTime.Now;
t.Interval = 1000;
t.Tick += T_Tick;
wc.DownloadFileAsync(new Uri("https://github.com/google/google-api-dotnet-client/archive/master.zip"), #"C:\Users\chkri\AppData\Local\Temp\master.zip");
}
private void T_Tick(object sender, EventArgs e)
{
if ((DateTime.Now - lastDownloaded).TotalMilliseconds > 1000)
{
wc.CancelAsync();
}
}
private void Wc_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
if (e.Error != null)
{
lblProgress.Text = e.Error.Message;
}
}
private void Wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
lastDownloaded = DateTime.Now;
lblProgress.Text = e.BytesReceived + "/" + e.TotalBytesToReceive;
}
I have the following:
wc.DownloadDataCompleted += Wc_DownloadDataCompleted;
FileStream f = File.OpenWrite(insLoc + "\\update.zip");
wc.DownloadDataAsync(new Uri("http://example.com/file.zip"), installLoc + "\\file.zip");
(and in a separate function)
private void Wc_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
if (e.Error == null) {
MessageBox.Show("Download success");
}
else { MessageBox.Show("Download failed"); }
f.Flush();
f.Dispose();
}
When I check the file it is supposed to be downloading to, it exists, but there is nothing in it (ie, it is 0 bytes). I have flushed the FileStream after the download has finished, so what is happening? I have a progress bar as well, and it slowly increases to 100% so I know it's downloading the ZIP file, and the "Download Success" messagebox is shown.
Thanks in advance!
You should use the callback function to save the resulting file (represented as a byte array passed as second argument):
wc.DownloadDataCompleted += Wc_DownloadDataCompleted;
wc.DownloadDataAsync(new Uri("http://example.com/file.zip"));
and then:
private void Wc_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
if (e.Error == null)
{
string resultFile = Path.Combine(insLoc, "update.zip");
System.IO.File.WriteAllBytes(resultFile, e.Result);
MessageBox.Show("Download success");
}
else
{
MessageBox.Show("Download failed");
}
}
Now you can quickly see that using this method the entire file is loaded in memory as a byte array before flushing it to the file system. This is hugely inefficient especially if you are downloading large files. For this reason it is recommended to use the DownloadFileAsync method instead.
wc.DownloadFileCompleted += Wc_DownloadFileCompleted;
string resultFile = Path.Combine(insLoc, "update.zip");
var uri = new Uri("http://example.com/file.zip");
wc.DownloadFileAsync(uri, resultFile);
and the corresponding callback:
private void Wc_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
if (e.Error == null)
{
MessageBox.Show("Download success");
}
else
{
MessageBox.Show("Download failed");
}
}
I would use DownloadFileTaskAsync method of WebClient which I find very simple to use..
await wc.DownloadFileTaskAsync(new Uri("http://example.com/file.zip"), installLoc + "\\file.zip");
That is all. If you want to see the download progress, you can attach to DownloadProgressChanged event.
im using the following code to download 50+ files from my webserver
private void button5_Click(object sender, EventArgs e)
{
WebClient client = new WebClient();
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
//downloads
client.DownloadFileAsync(new Uri("http://www.site.com/file/loc.file"), #"c:\app\loc ");
client.DownloadFileAsync(new Uri("http://www.site.com/file/loc.file"), #"c:\app\loc ");
client.DownloadFileAsync(new Uri("http://www.site.com/file/loc.file"), #"c:\app\loc ");
}
void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
double bytesIn = double.Parse(e.BytesReceived.ToString());
double totalBytes = double.Parse(e.TotalBytesToReceive.ToString());
double percentage = bytesIn / totalBytes * 100;
progressBar.Value = int.Parse(Math.Truncate(percentage).ToString());
}
private void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
MessageBox.Show("Game Update Finished!");
}
im wanting to download 1 file at a time with a continuing progress bar iv got most of the coding done but when i hit the "Download" button i get the following error
WebClient does not support concurrent I/O operations.
what do i need to do?
Feel the difference:
It CAN download multiple files in parallel manner (by one stream per
one file).
But it CAN'T download one file using multiple streams.
Here is example (MainWindow contain one button 'Start' and five progress bars):
public partial class MainWindow : Window
{
private WebClient _webClient;
private ProgressBar[] _progressBars;
private int _index = 0;
public MainWindow()
{
InitializeComponent();
_progressBars = new [] {progressBar1, progressBar2, progressBar3, progressBar4, progressBar5};
ServicePointManager.DefaultConnectionLimit = 5;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
Interlocked.Increment(ref _index);
if (_index > _progressBars.Length)
return;
_webClient = new WebClient();
_webClient.DownloadProgressChanged += WebClient_DownloadProgressChanged;
_webClient.DownloadFileCompleted += WebClient_DownloadFileCompleted;
_webClient.DownloadFileAsync(new Uri("http://download.thinkbroadband.com/5MB.zip"),
System.IO.Path.GetTempFileName(),
_index);
}
private void WebClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs args)
{
var index = (int) args.UserState;
_progressBars[index-1].Value = args.ProgressPercentage;
}
private void WebClient_DownloadFileCompleted(object sender, AsyncCompletedEventArgs args)
{
var index = (int)args.UserState;
MessageBox.Show(args.Error == null
? string.Format("Download #{0} completed!", index)
: string.Format("Download #{0} error!\n\n{1}", index, args.Error));
}
}
you are running multiple downloads in parallel with the same WebClient instance - the error tells you that this is NOT supported - you either:
use multiple instances of WebClient (one per parallel download)
OR
download one file after the other
Relevant information:
http://msdn.microsoft.com/en-us/library/system.net.webclient.aspx
http://msdn.microsoft.com/en-us/magazine/cc700359.aspx
WebClient does not support concurrent I/O operations (multiple downloads) per instance, so you need to create a separate WebClient instance for each download. You can still perform each download asynchronously and use the same event handlers.
I am pretty new to silverlight and was very surprised to see that only asynchronous file downloading can be done. Well, I've attempted to counter act this by just setting a flag and waiting on it to change..
This is my simple code
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
WebClient webClient = new WebClient();
webClient.DownloadProgressChanged +=
new DownloadProgressChangedEventHandler(webClient_DownloadProgressChanged);
webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
webClient.OpenReadAsync(new Uri("/trunk/internal/SilverLightInterface.ashx?xxid=XXX", UriKind.Relative));
while (XmlStateStream == null) { }
lblProgress.Content = "Done Loading";
}
void webClient_DownloadProgressChanged(object sender,
DownloadProgressChangedEventArgs e) {
lblProgress.Content = "Downloading " + e.ProgressPercentage + "%";
}
volatile Stream XmlStateStream = null;
void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
if (e.Error != null)
{
lblProgress.Content = "Error: " + e.Error.Message;
return;
}
XmlStateStream = e.Result;
}
This is causing Firefox to actually freeze up(which is extremely annoying when I'm doing other things while developing) (btw, kudos to firefox cause I tested it and firefox froze, but I didn't lose what I was typing here after restoring)
I don't understand why the while(XmlStateStream==null){} is causing a freeze up. Is there some attribute for locks or volatile(other than what I already have) or am I in the wrong part of the Silverlight page lifecycle or something?
I'm really confused as to why this is not working.
Also, this is silverlight 3.0
Most likely, this code is running in the UI thread that handles all of the web browser's interaction with the user. This is why you won't find any blocking operations - because anything that blocks will freeze the UI in exactly the same way that you saw! What's more, if the UI thread also handles network IO (which is common), then you'll deadlock here because the asynchronous operation you're waiting on will never finish.
I'm afraid you'll just have to rewrite your code as a state machine driven by asynchronous operations.
Whilst you need to get with the asynchronous nature of things in Silverlight you can use C# 3 syntax to keep things a bit more together:-
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
DownloadXmlStateStream();
}
void DownloadXmlStateStream()
{
WebClient webClient = new WebClient();
webClient.DownloadProgressChanged += (s, e) => {
lblProgress.Content = "Downloading " + e.ProgressPercentage + "%";
}
webClient.OpenReadCompleted += (s, e) => {
if (e.Error != null)
{
lblProgress.Content = "Error: " + e.Error.Message;
}
else
{
XmlStateStream = e.Result;
lblProgress.Content = "Done Loading";
}
}
webClient.OpenReadAsync(new Uri("/trunk/internal/SilverLightInterface.ashx?xxid=XXX", UriKind.Relative));
}
You need to use the DownloadFileCompleted event.
delete this:
while (XmlStateStream == null) { }
lblProgress.Content = "Done Loading";
add this:
webClient.DownloadFileCompleted +=
new DownloadFileCompletedEventHandler(webClient_DownloadFileCompleted);
and this:
void webClient_DownloadFileCompleted(object sender, AsyncCompletedEventHandler) {
lblProgress.Content = "Done Loading";
}
If you really must have synchronous downloading, you need to "poll" for the download being complete less often. Try calling System.Threading.Thread.Sleep() with a delay of 50-250ms from within your busy-wait loop.
Although this will reduce the wasteful CPU utilization of your code, it's possible that it will not fix the UI responsiveness problem. It depends on whether the thread that's calling your MainPage_Loaded is the only one that can call UI update events. If that's the case, then the UI simply can not update until that handler returns.
By blocking until the file is downloaded, you're not only blocking the UI thread of your silverlight app - you're also blocking the UI thread of the browser, it would seem.
All you really want to do (I presume) is stop your app doing anything until the download completes. Try adding this line to MainPage_Loaded:
LayoutRoot.IsHitTestVisible = false;
Remove your blocking while loop and the completed message (the last two lines).
In webClient_OpenReadCompleted, add:
LayoutRoot.IsHitTestVisible = true;
lblProgress.Content = "Done Loading.";
And everything should work the way I think you want.
Here's the full code:
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
WebClient webClient = new WebClient();
webClient.DownloadProgressChanged +=
new DownloadProgressChangedEventHandler(webClient_DownloadProgressChanged);
webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
webClient.OpenReadAsync(new Uri("/trunk/internal/SilverLightInterface.ashx?xxid=XXX", UriKind.Relative));
LayoutRoot.IsHitTestVisible = false;
}
void webClient_DownloadProgressChanged(object sender,
DownloadProgressChangedEventArgs e) {
lblProgress.Content = "Downloading " + e.ProgressPercentage + "%";
}
void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
if (e.Error != null)
{
lblProgress.Content = "Error: " + e.Error.Message;
return;
}
LayoutRoot.IsHitTestVisible = true;
lblProgress.Content = "Done Loading.";
}