How to fix download issues for some users using WebClient DownloadFileAsync? - c#

I created a Windows Form Application in C# that users of my game modification can use to download updates automatically.
The 'Launcher', as I call it, uses WebClient to download the updates. But the first release of the mod is very big (2,7 GB zipped). The launcher works perfect for me and most users, but for some users the extraction of the zip file logs an error where the file is corrupted and not readable.
I searched already on stack, and it is possible that the file might be corrupted or truncated due to bad internet connection. But how do I build in a method that fix that problem?
//Start downloading file
using (WebClient webClient = new WebClient())
{
webClient.DownloadFileCompleted += new
AsyncCompletedEventHandler(Client_DownloadFileCompleted);
webClient.DownloadProgressChanged += new
DownloadProgressChangedEventHandler(Client_DownloadProgressChanged);
webClient.DownloadFileAsync(new Uri("http://www.dagovaxgames.com/api/downloads/+ patch.path), downloadPath);
}
private void Client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
//install the update
InstallUpdate();
}
private void InstallUpdate()
{
var file = currentPatchPath;
//get the size of the zip file
fileInfo = new FileInfo(file);
_fileSize = fileInfo.Length;
installBackgroundWorker = new BackgroundWorker();
installBackgroundWorker.DoWork += ExtractFile_DoWork;
installBackgroundWorker.ProgressChanged += ExtractFile_ProgressChanged;
installBackgroundWorker.RunWorkerCompleted += ExtractFile_RunWorkerCompleted;
installBackgroundWorker.WorkerReportsProgress = true;
installBackgroundWorker.RunWorkerAsync();
}
EDIT, just showing install code so that you know I am using a backgroundworker to extract the zip.

Common approach here is to download large file in small chunks and put them together on client after completion. Using this approach you can: 1. run few downloads in parallel and 2. in case of problem with network you don't have to download entire file again, just download incomplete chunks.

I faced a similar issue many years ago and created a subclass of WebClient that uses the DownloadProgressChanged event and a timer to abort downloads that get hung up and cancels the download smoother than the underlying internet transport layer does. The code also supports a callback to notify the calling code of progress. I found that sufficient to smoothly handle occasional hiccups downloading 1GB-ish files.
The idea of breaking your download into multiple pieces also has merit. You could leverage a library such as 7-Zip to both create the chunks and piece them back together (many compression libraries have that feature; I'm personally most familiar with 7-Zip).
Here's the code I wrote. Feel free to use and/or modify in any way that's helpful to you.
public class JWebClient : WebClient, IDisposable
{
public int Timeout { get; set; }
public int TimeUntilFirstByte { get; set; }
public int TimeBetweenProgressChanges { get; set; }
public long PreviousBytesReceived { get; private set; }
public long BytesNotNotified { get; private set; }
public string Error { get; private set; }
public bool HasError { get { return Error != null; } }
private bool firstByteReceived = false;
private bool success = true;
private bool cancelDueToError = false;
private EventWaitHandle asyncWait = new ManualResetEvent(false);
private Timer abortTimer = null;
private bool isDisposed = false;
const long ONE_MB = 1024 * 1024;
public delegate void PerMbHandler(long totalMb);
public delegate void TaggedPerMbHandler(string tag, long totalMb);
public event PerMbHandler NotifyMegabyteIncrement;
public event TaggedPerMbHandler NotifyTaggedMegabyteIncrement;
public JWebClient(int timeout = 60000, int timeUntilFirstByte = 30000, int timeBetweenProgressChanges = 15000)
{
this.Timeout = timeout;
this.TimeUntilFirstByte = timeUntilFirstByte;
this.TimeBetweenProgressChanges = timeBetweenProgressChanges;
this.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(MyWebClient_DownloadFileCompleted);
this.DownloadProgressChanged += new DownloadProgressChangedEventHandler(MyWebClient_DownloadProgressChanged);
abortTimer = new Timer(AbortDownload, null, TimeUntilFirstByte, System.Threading.Timeout.Infinite);
}
protected void OnNotifyMegabyteIncrement(long totalMb)
{
NotifyMegabyteIncrement?.Invoke(totalMb);
}
protected void OnNotifyTaggedMegabyteIncrement(string tag, long totalMb)
{
NotifyTaggedMegabyteIncrement?.Invoke(tag, totalMb);
}
void AbortDownload(object state)
{
cancelDueToError = true;
this.CancelAsync();
success = false;
Error = firstByteReceived ? "Download aborted due to >" + TimeBetweenProgressChanges + "ms between progress change updates." : "No data was received in " + TimeUntilFirstByte + "ms";
asyncWait.Set();
}
private object disposeLock = new object();
void MyWebClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
if (cancelDueToError || isDisposed) return;
long additionalBytesReceived = e.BytesReceived - PreviousBytesReceived;
PreviousBytesReceived = e.BytesReceived;
BytesNotNotified += additionalBytesReceived;
if (BytesNotNotified > ONE_MB)
{
OnNotifyMegabyteIncrement(e.BytesReceived);
OnNotifyTaggedMegabyteIncrement(Tag, e.BytesReceived);
BytesNotNotified = 0;
}
firstByteReceived = true;
try
{
lock (disposeLock)
{
if (!isDisposed) abortTimer.Change(TimeBetweenProgressChanges, System.Threading.Timeout.Infinite);
}
}
catch (ObjectDisposedException) { } // Some strange timing issue causes this to throw now and then
}
public string Tag { get; private set; }
public bool DownloadFileWithEvents(string url, string outputPath, string tag = null)
{
Tag = tag;
asyncWait.Reset();
Uri uri = new Uri(url);
this.DownloadFileAsync(uri, outputPath);
asyncWait.WaitOne();
return success;
}
void MyWebClient_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (e.Error != null) success = false;
if (cancelDueToError || isDisposed) return;
asyncWait.Set();
}
protected override WebRequest GetWebRequest(Uri address)
{
var result = base.GetWebRequest(address);
result.Timeout = this.Timeout;
return result;
}
void IDisposable.Dispose()
{
lock (disposeLock)
{
isDisposed = true;
if (asyncWait != null) asyncWait.Dispose();
if (abortTimer != null) abortTimer.Dispose();
base.Dispose();
}
}
}

I fixed it using a combination of a BackgroundWorker and downloading in small chunks (following up mtkachenko's solution). It also checks if the length of the downloaded file is the same as the one on the server. Using that, it can continue the download where the connection was interupted.
private void DownloadPatch(Patch patch){
//using background worker now!
downloadBackgroundWorker = new BackgroundWorker();
downloadBackgroundWorker.DoWork += (sender, e) => DownloadFile_DoWork(sender, e, patch);
downloadBackgroundWorker.ProgressChanged += DownloadFile_ProgressChanged;
downloadBackgroundWorker.RunWorkerCompleted += DownloadFile_RunWorkerCompleted;
downloadBackgroundWorker.WorkerReportsProgress = true;
downloadBackgroundWorker.RunWorkerAsync();
}
private void DownloadFile_DoWork(object sender, DoWorkEventArgs e, Patch patch)
{
string startupPath = Application.StartupPath;
string downloadPath = Path.Combine(Application.StartupPath, patch.path);
string path = ("http://www.dagovaxgames.com/api/downloads/" + patch.path);
long iFileSize = 0;
int iBufferSize = 1024;
iBufferSize *= 1000;
long iExistLen = 0;
System.IO.FileStream saveFileStream;
// Check if file exists. If true, then check amount of bytes
if (System.IO.File.Exists(downloadPath))
{
System.IO.FileInfo fINfo =
new System.IO.FileInfo(downloadPath);
iExistLen = fINfo.Length;
}
if (iExistLen > 0)
saveFileStream = new System.IO.FileStream(downloadPath,
System.IO.FileMode.Append, System.IO.FileAccess.Write,
System.IO.FileShare.ReadWrite);
else
saveFileStream = new System.IO.FileStream(downloadPath,
System.IO.FileMode.Create, System.IO.FileAccess.Write,
System.IO.FileShare.ReadWrite);
System.Net.HttpWebRequest hwRq;
System.Net.HttpWebResponse hwRes;
hwRq = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(path);
hwRq.AddRange((int)iExistLen);
System.IO.Stream smRespStream;
hwRes = (System.Net.HttpWebResponse)hwRq.GetResponse();
smRespStream = hwRes.GetResponseStream();
iFileSize = hwRes.ContentLength;
//using webclient to receive file size
WebClient webClient = new WebClient();
webClient.OpenRead(path);
long totalSizeBytes = Convert.ToInt64(webClient.ResponseHeaders["Content-Length"]);
int iByteSize;
byte[] downBuffer = new byte[iBufferSize];
while ((iByteSize = smRespStream.Read(downBuffer, 0, downBuffer.Length)) > 0)
{
if (stopDownloadWorker == true)
{
autoDownloadReset.WaitOne();
}
saveFileStream.Write(downBuffer, 0, iByteSize);
long downloadedBytes = new System.IO.FileInfo(downloadPath).Length;
// Report progress, hint: sender is your worker
int percentage = Convert.ToInt32(100.0 / totalSizeBytes * downloadedBytes);
(sender as BackgroundWorker).ReportProgress(percentage, null);
}
}
As you can see, I report the progress, so that I have a working progress bar as well.
private void DownloadFile_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
statusTextLabel.Text = "Downloading updates for version " + currentDownloadingPatch + " (" + e.ProgressPercentage + "%)";
progressBar.Value = e.ProgressPercentage;
}

Related

CSHARP running while every X seconds but until bool is true

I would like to check updates from txt file on web server. and send a messagebox when there is version mismatch, I'm using this code and it works but I want it only to connect the server every 10 minutes and check the text file.
is there any option to do that?
maybe using another way to create this loop?
I also would like to send the messagebox only once
bool hasDisplayed = false;
private string UpdateCheckServer()
{
WebClient client = new WebClient();
Stream stream = client.OpenRead("http://localhost/update/update_version.txt");
StreamReader reader = new StreamReader(stream);
string serverversion = reader.ReadToEnd();
return serverversion;
}
private string UpdateCheckClient()
{
string ClientVersion = System.IO.File.ReadAllText("update_version.txt");
return ClientVersion;
}
private void UpdateCheckTimer()
{
while (!hasDisplayed)
{
if (starter.Enabled == true && UpdateCheckServer() == UpdateCheckClient())
{
//Nothing here
}
if (starter.Enabled == true && UpdateCheckServer() != UpdateCheckClient())
{
MessageBox.Show("not updated");
hasDisplayed = true;
}
}
An implementation based on timer can look like this:
private bool _equalVersion = true;
private System.Timers.Timer _timer = new System.Timers.Timer(1000 * 60 * 10); //millisecond * seconds * minutes
private void StartUpdateCheckTimer()
{
_timer.Elapsed += UpdateCheck;
_timer.Start();
}
private void UpdateCheck(object sender, ElapsedEventArgs e)
{
_timer.Stop();
_equalVersion = (UpdateCheckServer() == UpdateCheckClient());
if (!_equalVersion)
{
MessageBox.Show("not updated");
}
else
{
_timer.Start();
}
}
in this way you get a timer:
triggered each 10 minutes,
performing the check
notifying the version change (and stopping) or proceeding with the subsequent iterations
then you can add some more logic like for example reset of the check, more proper notification action (separating your check routine from the GUI part) and many more.
Another approach (as pointed out by #imsmn) can be to make your implementation System.Threading.Sleep based, but I'd suggest the first option based on timer.
After several tries and learning I managed to do that.
private string UpdateCheckServer()
{
WebClient client = new WebClient();
Stream stream = client.OpenRead("http://localhost/update/update_version.txt");
StreamReader reader = new StreamReader(stream);
string serverversion = reader.ReadToEnd();
return serverversion;
}
private string UpdateCheckClient()
{
string ClientVersion = System.IO.File.ReadAllText("update_version.txt");
return ClientVersion;
}
private void UpdateCheckTimer()
{
System.Windows.Forms.Timer t = new System.Windows.Forms.Timer();
t.Interval = 15000; // specify interval time as you want
t.Tick += new EventHandler(timer_Tick);
t.Start();
void timer_Tick(object sender, EventArgs e)
{
//Call method
if (starter.Enabled == true && UpdateCheckServer() != UpdateCheckClient())
{
t.Stop();
MessageBox.Show("not updated");
}
}
}

RestSharp DownloadData sync with progressbar

How can I download file and show downloading progress by ProgressBar in window form app?
RestClient client = new RestClient("http://127.0.0.1/");
RestRequest request = new RestRequest("/test/{FileName}");
request.AddParameter("FileName", "testFile.abc", ParameterType.UrlSegment);
string path = #"C:/Users/[user]/Desktop/testFile.abc";
var fileForDownload = client.DownloadData(request);
fileForDownload.SaveAs(path);
if (File.Exists(#"C:/Users/[user]/Desktop/testFile.abc"))
{
MessageBox.Show("done");
}
I write somethink like this but I don't know what now?
I think a better alternative would be to override FileStream to get count of bytes written to file:
string tempFile = Path.Combine(Configuration.DownloadFolder, "TEST.DATA");
using (var writer = new HikFileStream(tempFile))
{
writer.Progress += (w, e) => {
#if DEBUG
Console.Write(string.Format("\rProgress: {0} / {1:P2}", writer.CurrentSize, ((double)writer.CurrentSize) / finalFileSize));
#endif
};
request.ResponseWriter = (responseStream) => responseStream.CopyTo(writer);
var response = client.DownloadData(request);
}
where HikFileStream is:
class HikFileStream : FileStream
{
public HikFileStream(string path)
: base(path, FileMode.Create, FileAccess.Write, FileShare.None)
{
}
public long CurrentSize { get; private set; }
public event EventHandler Progress;
public override void Write(byte[] array, int offset, int count)
{
base.Write(array, offset, count);
CurrentSize += count;
var h = Progress;
if (h != null)
h(this, EventArgs.Empty);//WARN: THIS SHOULD RETURNS ASAP!
}
}
Sorry but you can't, because there is no event handler object in RestClient to take status of download data.
Here is an alternative way to do it;
//...
timer1.Interval = 1000; // 1 sec interval.
timer1.Start();
RestClient client = new RestClient("http://127.0.0.1/")
{
Timeout = 10 * 1000 //10 sec timeout time.
};
RestRequest request = new RestRequest("/test/{FileName}");
request.AddParameter("FileName", "testFile.abc", ParameterType.UrlSegment);
string path = #"C:/Users/[user]/Desktop/testFile.abc";
var fileForDownload = client.DownloadData(request);
fileForDownload.SaveAs(path);
if (File.Exists(#"C:/Users/[user]/Desktop/testFile.abc"))
{
MessageBox.Show("done");
}
progressBar1.Value = 100;
timer1.Stop();
}
public void timer1_Tick(object sender, EventArgs e)
{
if (progressBar1.Value <= 100)
{
progressBar1.Value += 10;
}
if (progressBar1.Value >= 100)
{
progressBar1.Value = 0;
}
}
Change the name of "timer1" for naming-best-practices. Good luck...

(Pause) Stop download without dropping connection

I want to be able to pause a download. I can stop them by dropping the existing connections.
What I'm referring to is almost similar to what's described here: https://superuser.com/questions/170509/whats-the-difference-in-using-pause-stop-in-%C2%B5torrent
My download class:
public class Download
{
public event EventHandler<DownloadStatusChangedEventArgs> DownloadStatusChanged;
public event EventHandler<DownloadProgressChangedEventArgs> DownloadProgressChanged;
public event EventHandler DownloadCompleted;
public bool stop = true; // by default stop is true
public void DownloadFile(string DownloadLink, string Path)
{
stop = false; // always set this bool to false, everytime this method is called
long ExistingLength = 0;
FileStream saveFileStream;
if (File.Exists(Path))
{
FileInfo fileInfo = new FileInfo(Path);
ExistingLength = fileInfo.Length;
}
if (ExistingLength > 0)
saveFileStream = new FileStream(Path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
else
saveFileStream = new FileStream(Path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
var request = (HttpWebRequest)HttpWebRequest.Create(DownloadLink);
request.Proxy = null;
request.AddRange(ExistingLength);
try
{
using (var response = (HttpWebResponse)request.GetResponse())
{
long FileSize = ExistingLength + response.ContentLength; //response.ContentLength gives me the size that is remaining to be downloaded
bool downloadResumable; // need it for sending empty progress
if ((int)response.StatusCode == 206)
{
Console.WriteLine("Resumable");
var downloadStatusArgs = new DownloadStatusChangedEventArgs();
downloadResumable = true;
downloadStatusArgs.ResumeSupported = downloadResumable;
OnDownloadStatusChanged(downloadStatusArgs);
}
else // sometimes a server that supports partial content will lose its ability to send partial content(weird behavior) and thus the download will lose its resumability
{
Console.WriteLine("Resume Not Supported");
ExistingLength = 0;
var downloadStatusArgs = new DownloadStatusChangedEventArgs();
downloadResumable = false;
downloadStatusArgs.ResumeSupported = downloadResumable;
OnDownloadStatusChanged(downloadStatusArgs);
// restart downloading the file from the beginning because it isn't resumable
// if this isn't done, the method downloads the file from the beginning and starts writing it after the previously half downloaded file, thus increasing the filesize and corrupting the downloaded file
saveFileStream.Dispose(); // dispose object to free it for the next operation
File.WriteAllText(Path, string.Empty); // clear the contents of the half downloaded file that can't be resumed
saveFileStream = saveFileStream = new FileStream(Path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); // reopen it for writing
}
using (var stream = response.GetResponseStream())
{
byte[] downBuffer = new byte[4096];
int byteSize = 0;
long totalReceived = byteSize + ExistingLength;
var sw = new Stopwatch();
sw.Start();
while ((byteSize = stream.Read(downBuffer, 0, downBuffer.Length)) > 0)
{
saveFileStream.Write(downBuffer, 0, byteSize);
totalReceived += byteSize;
var args = new DownloadProgressChangedEventArgs();
args.BytesReceived = totalReceived;
args.TotalBytesToReceive = FileSize;
float currentSpeed = totalReceived / (float)sw.Elapsed.TotalSeconds;
args.CurrentSpeed = currentSpeed;
if (downloadResumable == true)
{
args.ProgressPercentage = ((float)totalReceived / (float)FileSize) * 100;
long bytesRemainingtoBeReceived = FileSize - totalReceived;
args.TimeLeft = (long)(bytesRemainingtoBeReceived / currentSpeed);
}
else
{
//args.ProgressPercentage = Unknown;
//args.TimeLeft = Unknown;
}
OnDownloadProgressChanged(args);
if (stop == true)
return;
}
sw.Stop();
}
}
var completedArgs = new EventArgs();
OnDownloadCompleted(completedArgs);
saveFileStream.Dispose();
}
catch (WebException e)
{
string filename = System.IO.Path.GetFileName(Path);
Console.WriteLine(e.Message);
saveFileStream.Dispose();
return; //not needed because this is the last line of the method, but let's keep it here
}
}
public void StopDownload()
{
stop = true;
}
protected virtual void OnDownloadStatusChanged(DownloadStatusChangedEventArgs e)
{
EventHandler<DownloadStatusChangedEventArgs> handler = DownloadStatusChanged;
if (handler != null)
{
handler(this, e);
}
}
protected virtual void OnDownloadProgressChanged(DownloadProgressChangedEventArgs e)
{
EventHandler<DownloadProgressChangedEventArgs> handler = DownloadProgressChanged;
if (handler != null)
{
handler(this, e);
}
}
protected virtual void OnDownloadCompleted(EventArgs e)
{
EventHandler handler = DownloadCompleted;
if (handler != null)
{
handler(this, e);
}
}
}
public class DownloadStatusChangedEventArgs : EventArgs
{
public bool ResumeSupported { get; set; }
}
public class DownloadProgressChangedEventArgs : EventArgs
{
public long BytesReceived { get; set; }
public long TotalBytesToReceive { get; set; }
public float ProgressPercentage { get; set; }
public float CurrentSpeed { get; set; } // in bytes
public long TimeLeft { get; set; } // in seconds
}
Can anybody help me with this?
Note: I can already resume downloads, that's not what I want, if you were wondering.
In your code (you should copy it into your question, by the way), you have a loop that reads bytes from a stream. You have a flag that will stop the loop.
You do the exact same thing except for pausing instead of stopping. It could be another flag, but you will also need to know when to resume, so you need something for the loop to wait on. One fairly neat way to do this would be with a lock (mutex).
I don't know C# so I can't give you working code, but here is pseudocode for it. With your pointer to the reference docs, it looks like SemaphoreSlim is the simplest thing suitable for this purpose.
bool stop = false
bool paused = false
SemaphoreSlim pauseLock = new SemaphoreSlim(1)
method download():
while (stuff to read):
read from stream
write to file
if stop:
break
// This will do nothing if not paused, or will block if paused
pauseLock.Wait()
pauseLock.Release()
method stop():
stop = true
self.unpause() // stop waiting on lock if needed
method pause()
if not paused:
paused = true
// Note this cannot block for more than a moment
// since the download thread doesn't keep the lock held
pauseLock.Wait()
method unpause()
if paused:
paused = false
pauseLock.Release()
Of course, doing this may cause the server to drop your connection eventually (at which point you need to resume anyway, and you'll only notice this after unpausing).

can't download multiplie files using DownloadFileAsync

I am developing an application for my own, In fact this application is for downloading latest version of antivirus that we are using in our company
in this application I want to use DownloadFileAsync method for download my files but it's not working and I am getting this error :
WebClient does not support concurrent I/O operations.
this is my source code :
private static WebClient wc = new WebClient();
private static ManualResetEvent handle = new ManualResetEvent(true);
private DateTime myDate = new DateTime();
private void btn_test_Click(object sender, EventArgs e)
{
using (WebClient client = new WebClient())
{
client.Encoding = System.Text.Encoding.UTF8;
var doc = new HtmlAgilityPack.HtmlDocument();
ArrayList result = new ArrayList();
doc.LoadHtml(client.DownloadString("https://www.symantec.com/security_response/definitions/download/detail.jsp?gid=savce"));
foreach (var href in doc.DocumentNode.Descendants("a").Select(x => x.Attributes["href"]))
{
if (href == null) continue;
string s = href.Value;
Match m = Regex.Match(s, #"http://definitions.symantec.com/defs/(\d{8}-\d{3}-v5i(32|64)\.exe)");
if (m.Success)
{
Match date = Regex.Match(m.Value, #"(\d{4})(\d{2})(\d{2})");
Match filename = Regex.Match(m.Value, #"\d{8}-\d{3}-v5i(32|64)\.exe");
int year = Int32.Parse(date.Groups[0].Value);
int month = Int32.Parse(date.Groups[1].Value);
int day = Int32.Parse(date.Groups[3].Value);
myDate = new DateTime(
Int32.Parse(date.Groups[1].Value),
Int32.Parse(date.Groups[2].Value),
Int32.Parse(date.Groups[3].Value));
listBox1.Items.Add(m.Value);
if (myDate == DateTime.Now)
{
Download(m.Value,filename.Value);
}
else
{
MessageBox.Show("There is no Update!");
}
}
}
}
}
private void Download(string url, string fileName)
{
wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(wc_DownloadProgressChanged);
wc.DownloadFileAsync(new Uri(url), #"\\10.1.0.15\Symantec Update Weekly\\" + fileName);
//wc.DownloadFile(url, #"\\10.1.0.15\Symantec Update Weekly\\" + fileName);
}
private void WcOnDownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
if (!e.Cancelled && e.Error == null)
{
//async download completed successfully
}
handle.Set();
}
private void wc_DownloadProgressChanged(object sender, System.Net.DownloadProgressChangedEventArgs e)
{
double bytesIn = double.Parse(e.BytesReceived.ToString());
double totalBytes = double.Parse(e.TotalBytesToReceive.ToString());
double percentage = bytesIn / totalBytes * 100;
progressBar1.Value = int.Parse(Math.Truncate(percentage).ToString());
}
when my application trying to download files,
it seems that above method can not download multiple files in same time.
I searched a lot and find this solution but I could not apply that into my application.
how can I solve that.
thanks in your advise.
// Declare a field to hold the Task
private static Task DownloadTask;
private Task Download(string url, string fileName)
{
var wc = new WebClient();
wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(wc_DownloadProgressChanged);
return wc.DownloadFileTaskAsync(new Uri(url), #"\\10.1.0.15\Symantec Update Weekly\\" + fileName);
}
You'll probably need change the progressbar to handle multiple threads.
Inside btn_test_Click
// Before foreach
var tasks = new List<Task>();
// Inside foreach
if (myDate == DateTime.Now)
{
MessageBox.Show("Updates are New");
}
else
{
tasks.Add(Download(m.Value,filename.Value));
}
// After foreach
// You can also set the TimeSpan value and update the progressbar
// periodically until all the tasks are finished
DownloadTask = Task.WhenAll(tasks);
See Task.WaitAll, WebClient.DownloadFileTaskAsync

WebClient async stuck

I have this code:
autoResetEvent = new AutoResetEvent(false);
clien = new WebClient();
clien.Encoding = Encoding.UTF8;
clien.DownloadDataCompleted += new DownloadDataCompletedEventHandler(clien_DownloadDataCompleted);
clien.DownloadDataAsync(new Uri("http://www.classoneequipment.com/"));
autoResetEvent.WaitOne();
void clien_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
Encoding enc = Encoding.UTF8;
myString = enc.GetString(e.Result);
autoResetEvent.Set();
}
When I run this code in a button click event - It works fine. but when I run the code from big class it gets stucked and don't arrive to the func: clien_DownloadDataCompleted.
You approach it all wrong. If you want to download 3000 or more pages you got to be a bit more efficient and not wait on the event from each download.
Implementation (just a rough draft, you have to complete it)
public class Downloader
{
ConcurrentQueue<Uri> _queue = new ConcurrentQueue<Uri>();
int _maxWorkers = 10;
long _currentWorkers = 0;
ManualResetEvent _completed;
public void Enqueue(Uri uri)
{
_queue.Enqueue(uri);
if (Interlocked.Read(_currentWorkers) < _maxWorkers)
{
// not very thread safe, but we just want to limit the workers
// within a reasonable limit. 1 or 2 more doesn't really matter.
Interlocked.Increment(_currentWorkers);
// yes, i'm a bit old fashioned.
TriggerJob();
}
}
private void TriggerJob()
{
Uri uri;
if (!_queue.TryDequeue(out uri))
{
Interlocked.Decrement(_currentWorkers);
return;
}
var client = new WebClient();
client.Encoding = Encoding.UTF8;
client.DownloadDataCompleted += DownloadDataCompleted;
client.DownloadDataAsync(uri);
}
private void DownloadDataCallback(object sender, DownloadDataCompletedEventArgs e)
{
try
{
// If the request was not canceled and did not throw
// an exception, display the resource.
if (!e.Cancelled && e.Error == null)
{
var args = new DownloadedEventArgs { uri = e.Uri, data = (byte[])e.result};
DownloadCompleted(this, args)
}
else
{
var args = new DownloadFailedEventArgs { uri = e.Uri, error = e.Error };
DownloadFailed(this, args);
}
}
catch (Exception err)
{
var args = new DownloadFailedEventArgs { uri = e.Uri, error = err };
DownloadFailed(this, args);
}
TriggerJob();
}
public event EventHandler<DownloadedEventArgs> DownloadCompleted = delegate{};
public event EventHandler<DownloadFailedEventArgs> DownloadFailed = delegate{};
}
public class DownloadedEventArgs
{
public Uri uri;
public byte[] data;
}
public class DownloadFailedEventArgs
{
public Uri uri;
public Exception error;
}
Usage:
var downloader = new Downloader();
downloader.Completed += (o,e) { Console.WriteLine("Whoohoho, completed: " + e.Uri); };
for (x = 1; x < 100000; x++)
{
downloader.Enqueue(new Uri("http://somewhere.com));
}

Categories

Resources