I added an event handler to the WebClient's DownloadProgressChanged event, but it never seems to fire. The file successfully downloads, but without having its progress updated.
public class DownloadFile
{
private File file = null;
public DownloadFile(File file)
{
this.file = file;
}
public void startDownloadThread()
{
Console.WriteLine("Starting Download : "+file.URL);
var t = new Thread(() => DownloadThread(file));
t.Start();
}
public Action<string> action_error_downloadFailed = Console.WriteLine;
private void DownloadThread(File file) //Unnecessary argument but whatever ;D
{
try
{
string url = file.URL;
string savepath = file.DestinationDir + "\\" + file.Filename;
WebClient_B client = new WebClient_B();
client.Proxy = null; //default to no proxy
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
client.DownloadFile(url, savepath);
Console.WriteLine("Download finished :" + file.Filename);
}
catch (Exception ex)
{
if (action_error_downloadFailed != null)
action_error_downloadFailed("Download failed :"+ex.Message);
}
}
private void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
try
{
if (file.TotalSize == 0)
file.TotalSize = (int)e.TotalBytesToReceive;
file.CurrentSize = (int)e.BytesReceived;
Form_DownloadManager.rebuildQueue();
Console.WriteLine("{0} downloaded {1} of {2} bytes. {3} % complete...",
(string)e.UserState,
e.BytesReceived,
e.TotalBytesToReceive,
e.ProgressPercentage);
}
catch (Exception ex) { Console.WriteLine("client_DownloadProgressChanged error : "+ex.Message); }
}
}
Output:
Starting Download : http://x.x.x/y/z.zip
'projectname.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
The thread '<No Name>' (0x3b8c) has exited with code 0 (0x0).
Download finished :z.zip
I am using WebClient_B because I had to add useragent+cookiecontainer functionality to the WebClient class since my server kept rejecting the download request. The event never fired with the 'standard' WebClient class either, though. So that shouldn't be the problem. But anyway; link to class
client.DownloadFile(url, savepath);
You have to use the async version to download a file, currently you use the blocking, synchronous version.
From the msdn docs for WebClient.DownloadProgressChanged:
Occurs when an asynchronous download operation successfully transfers
some or all of the data.
In your case that would be:
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
client.DownloadFileAsync (url, savepath);
Since your method does not directly return any result, this refactoring shouldn't be a problem, note though that the download most likely has not been completed by the time the method returns to the caller.
If you want to use WebClient synchronously, while getting progress updates, you can rely on this method detailed here:
http://alexfeinberg.wordpress.com/2014/09/14/how-to-use-net-webclient-synchronously-and-still-receive-progress-updates/
public void DownloadFile(Uri uri, string destination)
{
using (var wc = new WebClient())
{
wc.DownloadProgressChanged += HandleDownloadProgress;
wc.DownloadFileCompleted += HandleDownloadComplete;
var syncObject = new object();
lock (syncObject)
{
wc.DownloadFileAsync(uri, destination, syncObject);
// This would block the thread until download completes
Monitor.Wait(syncObject);
}
}
// Do more stuff after download was complete
}
private void HandleDownloadComplete(object sender, AsyncCompletedEventArgs args)
{
lock (args.UserState)
{
// releases blocked thread
Monitor.Pulse(args.UserState);
}
}
private void HandleDownloadProgress(object sender, DownloadProgressChangedEventArgs args)
{
// Process progress updates here
}
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).
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.
I have a WinForms application which uses a backgroundworker for downloading images from given urls. For the download I use a backgroundworker.
The application is running fine when started, and the download happens as planned, but when the worker is done and I click the downloadbutton again to start downloading from another url, the backgroundworker doesn't do anything.
I fixed that problem temporarily by calling application.restart() when the worker is done, which works but can't be here longer than it has to.
Worker-Code:
// initialization of worker is done in constructor of my class
downloadWorker.WorkerReportsProgress = true;
downloadWorker.WorkerSupportsCancellation = true;
downloadWorker.DoWork += new DoWorkEventHandler(worker_doWork);
downloadWorker.ProgressChanged += new ProgressChangedEventHandler(worker_progressChanged);
downloadWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_runWorkerCompleted);
// ...
private void worker_doWork(object sender, DoWorkEventArgs e)
{
WebClient downloadClient = new WebClient();
HttpWebRequest HttpReq = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response;
try
{
response = (HttpWebResponse)HttpReq.GetResponse();
}
catch (WebException ex)
{
response = (HttpWebResponse)ex.Response;
}
if (response.StatusCode == HttpStatusCode.NotFound)
MessageBox.Show("Website not found");
if (response.StatusCode == HttpStatusCode.OK)
{
for(int i=0; i<3;i++)
{
string image = getImageUrl(url,i);
downloadWorker.ReportProgress(i);
image = WebUtility.HtmlDecode(image);
string saveName = "img_"+i+".png";
try
{
downloadClient.DownloadFile(image, saveName);
}
catch (Exception ex)
{
MessageBox.Show(ex.StackTrace);
}
}
}
}
private void worker_progressChanged(object sender, ProgressChangedEventArgs e)
{
rtxtStatus.AppendText("Downloade Image" + e.ProgressPercentage + " of 3");
}
private void worker_runWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Download completed");
}
edit:
if (e.Error != null)
{
MessageBox.Show(e.Error.ToString());
}
To avoid any misunderstandings: The backgroundWorker is definetely running at the second time, and it is not an error of the reportProgress-method, since I get the same thing when I dont report anything.
edit no. 2:
I found out where the error came from: at the second run, the for-loop is completely skipped. But that doesn't make any sense for me either... There can't be any other value still be in because I have a completely new instance of the class, can it? But anyway, if it just skipped the method the worker still should exit which it doesn't do. For testing, I added a MessageBox after the for-loop, which is not executed after the second run.
How can I pause the loop in someRandomMethod() until the code in DownloadCompleted() have been executed? This code below only unpacks the latest version in the versions array. It's like the loop is faster than the first download and m_CurrentlyDownloading have the latest value the first time DownloadCompleted() is beeing executed.
private void someRandomMethod() {
for (int i = 0; i < versions.Count; i++)
{
//ClearInstallFolder();
m_CurrentlyDownloading = versions.ElementAt(i);
Download(versions.ElementAt(i));
LocalUpdate(versions.ElementAt(i));
System.Threading.Thread.Sleep(500);
}
}
private void Download(string p_Version)
{
string file = p_Version + ".zip";
string url = #"http://192.168.56.5/" + file;
//client is global in the class
client = new WebClient();
client.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadCompleted);
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgressChanged);
client.DownloadFileAsync(new Uri(url), #"C:\tmp\" + file);
}
private void DownloadCompleted(object sender, AsyncCompletedEventArgs e)
{
if (e.Error == null)
{
Unpack(m_CurrentlyDownloading);
if (GetInstalledVersion() == GetLatestVersion())
ClearZipFiles();
}
else
MessageBox.Show(e.Error.ToString());
}
The easiest way would be to not use the *async methods. The normal DownloadFile will pause execution until it completes.
But if you've got access to the Await keyword, try this.
private async Task Download(string p_Version)
{
string file = p_Version + ".zip";
string url = #"http://192.168.56.5/" + file;
//client is global in the class
client = new WebClient();
client.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadCompleted);
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgressChanged);
await client.DownloadFileAsync(new Uri(url), #"C:\tmp\" + file);
}
something like this can be used to wait
make it class property
bool IsDownloadCompleted=false;
Add this in DownloadCompletedEvent
IsDownloadCompleted=true;
and this where you want to stop loop
while(DownloadCompleted!=true)
{
Application.DoEvents();
}
Create some boolean variable, create a delegate and get\set methods for this variable.
Then just in loop made smth like :
while(!isDownLoadCompleted)Thread.Sleep(1024);
You Can use Paralel.ForEach. this loop will wait until all threads done.
check Here for how to use :
http://msdn.microsoft.com/tr-tr/library/dd460720(v=vs.110).aspx
or
http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx
I am using VSTS 2008 + C# + .Net 3.5 + ASP.Net + IIS 7.0 to develop a Windows Forms application at client side to upload a file, and at server side I receive this file using an aspx file.
I find my client side application will hang after click the button to trigger upload event. Any ideas what is wrong and how to solve? Thanks!
Client side code,
public partial class Form1 : Form
{
private static WebClient client = new WebClient();
private static ManualResetEvent uploadLock = new ManualResetEvent(false);
private static void Upload()
{
try
{
Uri uri = new Uri("http://localhost/Default2.aspx");
String filename = #"C:\Test\1.dat";
client.Headers.Add("UserAgent", "TestAgent");
client.UploadProgressChanged += new UploadProgressChangedEventHandler(UploadProgressCallback);
client.UploadFileCompleted += new UploadFileCompletedEventHandler(UploadFileCompleteCallback);
client.UploadFileAsync(uri, "POST", filename);
uploadLock.WaitOne();
}
catch (Exception e)
{
Console.WriteLine(e.StackTrace.ToString());
}
}
public static void UploadFileCompleteCallback(object sender, UploadFileCompletedEventArgs e)
{
Console.WriteLine("Completed! ");
uploadLock.Set();
}
private static void UploadProgressCallback(object sender, UploadProgressChangedEventArgs e)
{
Console.WriteLine("{0} uploaded {1} of {2} bytes. {3} % complete...",
(string)e.UserState,
e.BytesSent,
e.TotalBytesToSend,
e.ProgressPercentage);
// Console.WriteLine (e.ProgressPercentage);
}
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Upload();
}
}
Server side code:
protected void Page_Load(object sender, EventArgs e)
{
string agent = HttpContext.Current.Request.Headers["UserAgent"];
using (FileStream file = new FileStream(#"C:\Test\Agent.txt", FileMode.Append, FileAccess.Write))
{
byte[] buf = Encoding.UTF8.GetBytes(agent);
file.Write(buf, 0, buf.Length);
}
foreach (string f in Request.Files.AllKeys)
{
HttpPostedFile file = Request.Files[f];
file.SaveAs("C:\\Test\\UploadFile.dat");
}
}
you are waiting in the main windows events thread, so your GUI will be frozen.
Try this (using non static methods allows you to use the Control.Invoke method to run callbacks on the windows GUI thread and free this thread in order to redraw)
public partial class Form1 : Form
{
private static WebClient client = new WebClient();
private static ManualResetEvent uploadLock = new ManualResetEvent(false);
private void Upload()
{
try
{
Cursor=Cursors.Wait;
Uri uri = new Uri("http://localhost/Default2.aspx");
String filename = #"C:\Test\1.dat";
client.Headers.Add("UserAgent", "TestAgent");
client.UploadProgressChanged += new UploadProgressChangedEventHandler(UploadProgressCallback);
client.UploadFileCompleted += new UploadFileCompletedEventHandler(UploadFileCompleteCallback);
client.UploadFileAsync(uri, "POST", filename);
}
catch (Exception e)
{
Console.WriteLine(e.StackTrace.ToString());
this.Cursor=Cursors.Default;
this.Enabled=false;
}
}
public void UploadFileCompleteCallback(object sender, UploadFileCompletedEventArgs e)
{
// this callback will be invoked by the async upload handler on a ThreadPool thread, so we cannot touch anything GUI-related. For this we have to switch to the GUI thread using control.BeginInvoke
if(this.InvokeRequired)
{
// so this is called in the main GUI thread
this.BeginInvoke(new UploadFileCompletedEventHandler(UploadFileCompleteCallback); // beginInvoke frees up the threadpool thread faster. Invoke would wait for completion of the callback before returning.
}
else
{
Cursor=Cursors.Default;
this.enabled=true;
MessageBox.Show(this,"Upload done","Done");
}
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Upload();
}
}
}
And do the same thing in your progress (you could update a progressbar indicator for example).
Cheers,
Florian