Welcome to Stack Overflow...
By examining your loop it is apparent that you are locking the UI thread in your loops.
while (!downloader.IsAlive) ;
while (!downloadFavIcon_Completed) ;
Is the reason why your UI locks up. Ideally this would be a job for the background worker as it is designed to run in the background and provides events to plumb back to your UI thread. This is possible to write using a Thread however the background worker should be used.
Now if you really wanted to write this using a Thread
object then I would suggest you make a class for your downloader and create events. Therefore we can write simple events such as
- Completed event
- Cancelled Event
- UI Progress Event
I DONT RECOMMEND THIS APPROACH (read further down)
Simply start by creating a new class Downloader
and here is a sample (bare minimum)
public class Downloader
{
/// <summary>
/// Delegate Event Handler for the downloading progress
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void DownloaderProgressEventHandler(Downloader sender, DownloaderProgressEventArgs e);
/// <summary>
/// Delegate Event Handler for the completed event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void DownloaderCompletedEventHandler(Downloader sender, DownloaderCompletedEventArgs e);
/// <summary>
/// The completed event
/// </summary>
public event DownloaderCompletedEventHandler Completed;
/// <summary>
/// The cancelled event
/// </summary>
public event EventHandler Cancelled;
/// <summary>
/// the progress event
/// </summary>
public event DownloaderProgressEventHandler Progress;
/// <summary>
/// the running thread
/// </summary>
Thread thread;
/// <summary>
/// the aborting flag
/// </summary>
bool aborting = false;
//the addresses
String[] addr = new String[] {
"http://google.com/favicon.ico",
"http://microsoft.com/favicon.ico",
"http://freesfx.com/favicon.ico",
"http://yahoo.com/favicon.ico",
"http://downloadha.com/favicon.ico",
"http://hp.com/favicon.ico",
"http://bing.com/favicon.ico",
"http://webassign.com/favicon.ico",
"http://youtube.com/favicon.ico",
"https://twitter.com/favicon.ico",
"http://cc.com/favicon.ico",
"http://stackoverflow.com/favicon.ico",
"http://vb6.us/favicon.ico",
"http://facebook.com/favicon.ico",
"http://flickr.com/favicon.ico",
"http://linkedin.com/favicon.ico",
"http://blogger.com/favicon.ico",
"http://blogfa.com/favicon.ico",
"http://metal-archives.com/favicon.ico",
"http://wordpress.com/favicon.ico",
"http://metallica.com/favicon.ico",
"http://wikipedia.org/favicon.ico",
"http://visualstudio.com/favicon.ico",
"http://evernote.com/favicon.ico"
};
/// <summary>
/// Starts the downloader
/// </summary>
public void Start()
{
if (this.aborting)
return;
if (this.thread != null)
throw new Exception("Already downloading....");
this.aborting = false;
this.thread = new Thread(new ThreadStart(runDownloader));
this.thread.Start();
}
/// <summary>
/// Starts the downloader
/// </summary>
/// <param name="addresses"></param>
public void Start(string[] addresses)
{
if (this.aborting)
return;
if (this.thread != null)
throw new Exception("Already downloading....");
this.addr = addresses;
this.Start();
}
/// <summary>
/// Aborts the downloader
/// </summary>
public void Abort()
{
if (this.aborting)
return;
this.aborting = true;
this.thread.Join();
this.thread = null;
this.aborting = false;
if (this.Cancelled != null)
this.Cancelled(this, EventArgs.Empty);
}
/// <summary>
/// runs the downloader
/// </summary>
void runDownloader()
{
Bitmap favCollection = new Bitmap(96, 64);
Graphics g = Graphics.FromImage(favCollection);
for (var i = 0; i < this.addr.Length; i++)
{
if (aborting)
break;
using (System.Net.WebClient client = new System.Net.WebClient())
{
try
{
byte[] dl = client.DownloadData(addr[i]);
using (var stream = new MemoryStream(dl))
{
using (var dlImg = new Bitmap(stream))
{
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
}
}
catch (Exception)
{
using (var dlImg = new Bitmap(Properties.Resources.defaultFacIcon))
{
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
}
}
if (aborting)
break;
if (this.Progress != null)
this.Progress(this, new DownloaderProgressEventArgs
{
Completed = i + 1,
Total = this.addr.Length
});
}
if (!aborting && this.Completed != null)
{
this.Completed(this, new DownloaderCompletedEventArgs
{
Bitmap = favCollection
});
}
this.thread = null;
}
/// <summary>
/// Downloader progress event args
/// </summary>
public class DownloaderProgressEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the completed images
/// </summary>
public int Completed { get; set; }
/// <summary>
/// Gets or sets the total images
/// </summary>
public int Total { get; set; }
}
/// <summary>
/// Downloader completed event args
/// </summary>
public class DownloaderCompletedEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the bitmap
/// </summary>
public Bitmap Bitmap { get; set; }
}
}
Now that is allot of code but lets quickly look at it. To start with we have defined 2 delegates for our Completed and Progress events. These delegates accept the downloader instance as the sender and special event arg classes listed at the bottom. Followed by our 3 events (as listed above) these events will be used to signal changes to the downloader.
Next we have our fields defined.
Thread thread;
This is a reference to the thread that will be created when the 'Start()` methods are called.
bool aborting = false;
This is a simple flag to signal to the thread that we should abort. Now I have decided to use a flag and let the thread gracefully finish as opposed to calling the Thread.Abort()
method. This ensures all the clean up can properly take place.
string[] addres =....
Our initial addresses.
Now this is all simple so far. Next comes our Start()
methods. I have provided two different methods. One of the methods accepts a new string[]
of addresses to download (not that important).
You will notice in this method
/// <summary>
/// Starts the downloader
/// </summary>
public void Start()
{
if (this.aborting)
return;
if (this.thread != null)
throw new Exception("Already downloading....");
this.aborting = false;
this.thread = new Thread(new ThreadStart(runDownloader));
this.thread.Start();
}
The first thing we do is check if the aborting flag is set. If it is then ignore the start call (you could throw an exception). Next we check if the thread is not null. If the thread is not null then our downloader is running. Finally we simply reset our aborting flag to false
and start our new Thread
.
Moving down to the Abort()
method. This method will first check if the aborting
flag is set. If so then do-nothing. Next we set our aborting
flag to true. The next step, and I will warn you this WILL block your calling thread is call the method Thread.Join()
which will join the thread to our calling thread. Basically waits for the thread to exit.
Finally we simply set the thread instance to null, reset the aborting
flag to false and trigger the Cancelled
event (if subscribed).
Next comes the main method that does the downloading. First off you will notice I moved around your variables and used using
statements for the disposable objects. (That is another topic).
The big piece to the runDownloader()
method is that it periodically checks the 'aborting' flag. If this flag is ever set to true
the downloader
stops there. Now note you may have a situation where the abort is called while the WebClient
is downloading an image. Ideally you would let your WebClient
finish the request, dispose of correctly then exit the loops.
After each download of the image the progress event is fired (if subscribed to). Finally when the iterations are finished and all images downloaded the "Completed" event is fired with the compiled bitmap image.
PAUSE FOR A BREATHE
Now this is all great.. BUT how do you use it. Well simple I created a form with a button, progress bar and picturebox. The button will be used to start and stop the downloader, progress bar to handle the progress events and the picture box for the finished image.
Here is a sample program i have commented parts of it.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.progressBar1.Visible = false;
this.progressBar1.Enabled = false;
}
Downloader downloader;
/// <summary>
/// starts \ stop button pressed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
//if downloader is not null then abort it
if (downloader != null)
{
downloader.Abort();
return;
}
//setup and start the downloader
this.progressBar1.Value = 0;
this.progressBar1.Minimum = 0;
this.progressBar1.Enabled = true;
this.progressBar1.Visible = true;
this.downloader = new Downloader();
this.downloader.Progress += downloader_Progress;
this.downloader.Completed += downloader_Completed;
this.downloader.Cancelled += downloader_Cancelled;
this.downloader.Start();
}
/// <summary>
/// downloader cancelled event handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void downloader_Cancelled(object sender, EventArgs e)
{
this.unhookDownloader();
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate
{
this.progressBar1.Enabled = false;
this.progressBar1.Visible = false;
MessageBox.Show(this, "Cancelled");
});
else
{
this.progressBar1.Enabled = false;
this.progressBar1.Visible = false;
MessageBox.Show(this, "Cancelled");
}
}
/// <summary>
/// downloader completed event handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void downloader_Completed(Downloader sender, Downloader.DownloaderCompletedEventArgs e)
{
this.unhookDownloader();
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate
{
this.progressBar1.Enabled = false;
this.progressBar1.Visible = false;
this.pictureBox1.Image = e.Bitmap;
MessageBox.Show(this, "Completed");
});
else
{
this.progressBar1.Enabled = false;
this.progressBar1.Visible = false;
this.pictureBox1.Image = e.Bitmap;
MessageBox.Show(this, "Completed");
}
}
/// <summary>
/// downloader progress event handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void downloader_Progress(Downloader sender, Downloader.DownloaderProgressEventArgs e)
{
if (this.progressBar1.InvokeRequired)
this.progressBar1.Invoke((MethodInvoker)delegate
{
this.progressBar1.Value = e.Completed;
this.progressBar1.Maximum = e.Total;
});
else
{
this.progressBar1.Value = e.Completed;
this.progressBar1.Maximum = e.Total;
}
}
/// <summary>
/// unhooks the events handlers and sets the downloader to null
/// </summary>
void unhookDownloader()
{
this.downloader.Progress -= downloader_Progress;
this.downloader.Completed -= downloader_Completed;
this.downloader.Cancelled -= downloader_Cancelled;
this.downloader = null;
}
}
This is a simple implementation of how you could use the Thread
object to do your work. In my opinion this is way too much work. Now lets do it in a Background Worker.
I STRONGLY RECOMMEND THIS APPROACH
Why you might say? Well with the Background Worker provides us with the tested and supported methods (plus more) that we tried to implement. You will note that the actual work of downloading the images and detecting cancellation is relatively the same. However you will notice that I am not worried (as much) about cross thread issues when posting the completed and progress events.
private void button2_Click(object sender, EventArgs e)
{
if (this.worker != null && this.worker.IsBusy)
{
this.worker.CancelAsync();
return;
}
string[] addr = new string[] {".... our full addresss lists" };
this.progressBar1.Maximum = addr.Length;
this.progressBar1.Value = 0;
this.progressBar1.Visible = true;
this.progressBar1.Enabled = true;
this.worker = new BackgroundWorker();
this.worker.WorkerSupportsCancellation = true;
this.worker.WorkerReportsProgress = true;
this.worker.ProgressChanged += (s, args) =>
{
this.progressBar1.Value = args.ProgressPercentage;
};
this.worker.RunWorkerCompleted += (s, args) =>
{
this.progressBar1.Visible = false;
this.progressBar1.Enabled = false;
if (args.Cancelled)
{
MessageBox.Show(this, "Cancelled");
worker.Dispose();
worker = null;
return;
}
var img = args.Result as Bitmap;
if (img == null)
{
worker.Dispose();
worker = null;
return;
}
this.pictureBox1.Image = img;
MessageBox.Show(this, "Completed");
worker.Dispose();
worker = null;
};
this.worker.DoWork += (s, args) =>
{
Bitmap favCollection = new Bitmap(96, 64);
Graphics g = Graphics.FromImage(favCollection);
for (var i = 0; i < addr.Length; i++)
{
if (worker.CancellationPending)
break;
using (System.Net.WebClient client = new System.Net.WebClient())
{
try
{
byte[] dl = client.DownloadData(addr[i]);
using (var stream = new MemoryStream(dl))
{
using (var dlImg = new Bitmap(stream))
{
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
}
}
catch (Exception)
{
using (var dlImg = new Bitmap(Properties.Resources.defaultFacIcon))
{
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
}
}
if (worker.CancellationPending)
break;
this.worker.ReportProgress(i);
}
if (worker.CancellationPending)
{
g.Dispose();
favCollection.Dispose();
args.Cancel = true;
return;
}
args.Cancel = false;
args.Result = favCollection;
};
worker.RunWorkerAsync();
}
I hope this helps understand a few possible ways to implement what you would like to achieve.
-Nico