Добро пожаловать в Stack Overflow...
Изучив свой цикл, становится очевидным, что вы блокируете поток пользовательского интерфейса в своих циклах.
while (!downloader.IsAlive) ;
while (!downloadFavIcon_Completed) ;
Это причина, по которой ваш пользовательский интерфейс блокируется. В идеале это должна быть работа для фонового работника, поскольку он предназначен для работы в фоновом режиме и предоставляет события для обратной связи с вашим потоком пользовательского интерфейса. Это можно написать с помощью потока, однако следует использовать фонового рабочего.
Теперь, если вы действительно хотите написать это, используя объект Thread
, я предлагаю вам создать класс для вашего загрузчика и создать события. Поэтому мы можем написать простые события, такие как
- Завершенное событие
- Отмененное событие
- Событие прогресса пользовательского интерфейса
Я НЕ РЕКОМЕНДУЮ ЭТОТ ПОДХОД (читайте далее)
Просто начните с создания нового класса Downloader
, и вот пример (минимум)
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; }
}
}
Теперь это выделение кода, но давайте быстро посмотрим на него. Для начала мы определили двух делегатов для наших событий Completed и Progress. Эти делегаты принимают экземпляр загрузчика в качестве отправителя и специальных классов аргументов событий, перечисленных внизу. Вслед за нашими 3 событиями (как указано выше) эти события будут использоваться для сигнализации об изменениях в загрузчике.
Далее мы определяем наши поля.
Thread thread;
Это ссылка на поток, который будет создан при вызове методов Start().
bool aborting = false;
Это простой флаг, сигнализирующий потоку, что мы должны прервать его выполнение. Теперь я решил использовать флаг и позволить потоку изящно завершиться, а не вызывать метод Thread.Abort()
. Это гарантирует, что вся уборка может быть проведена должным образом.
string[] addres =....
Наши первоначальные адреса.
Вот пока все просто. Далее идут наши Start()
методы. Я предоставил два разных метода. Один из методов принимает новые string[]
адресов для загрузки (не так уж важно).
Вы заметите в этом методе
/// <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();
}
Первое, что мы делаем, это проверяем, установлен ли флаг прерывания. Если это так, то игнорируйте вызов start (вы можете создать исключение). Затем мы проверяем, не является ли поток нулевым. Если поток не нулевой, то наш загрузчик работает. Наконец, мы просто сбрасываем наш прерывающий флаг на false
и запускаем новый Thread
.
Переходим к методу Abort()
. Этот метод сначала проверит, установлен ли флаг aborting
. Если так, то ничего не делать. Затем мы устанавливаем для нашего флага aborting
значение true. Следующим шагом, и я предупреждаю вас, что БУДЕТ блокировать вызывающий поток, является вызов метода Thread.Join()
, который присоединит поток к нашему вызывающему потоку. В основном ждет выхода потока.
Наконец, мы просто устанавливаем экземпляр потока в null, сбрасываем флаг aborting
в false и запускаем событие Cancelled
(если подписано).
Далее идет основной метод, который выполняет загрузку. Во-первых, вы заметите, что я переместил ваши переменные и использовал операторы using
для одноразовых объектов. (это уже другая тема).
Большая часть метода runDownloader()
заключается в том, что он периодически проверяет флаг прерывания. Если этот флаг когда-либо установлен на true
, downloader
останавливается на этом. Теперь обратите внимание, что у вас может возникнуть ситуация, когда прерывание вызывается, когда WebClient
загружает изображение. В идеале вы должны позволить своему WebClient
завершить запрос, правильно избавиться от него, а затем выйти из цикла.
После каждой загрузки изображения запускается событие progress (если на него оформлена подписка). Наконец, когда итерации завершены и все изображения загружены, запускается событие «Completed» с скомпилированным растровым изображением.
ПАУЗА ПЕРЕДЫШАТЬ
Теперь это все здорово .. НО как вы это используете. Ну просто я создал форму с кнопкой, индикатором выполнения и картинкой. Кнопка будет использоваться для запуска и остановки загрузчика, индикатор выполнения для обработки событий выполнения и окно изображения для готового изображения.
Вот пример программы, которую я прокомментировал.
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;
}
}
Это простая реализация того, как вы можете использовать объект Thread
для выполнения своей работы. На мой взгляд, это слишком много работы. Теперь давайте сделаем это в Background Worker.
Я НАСТОЯТЕЛЬНО РЕКОМЕНДУЮ ЭТОТ ПОДХОД
Почему вы могли бы сказать? Well with Background Worker предоставляет нам протестированные и поддерживаемые методы (и многое другое), которые мы пытались реализовать. Вы заметите, что фактическая работа по загрузке изображений и обнаружению отмены относительно одинакова. Однако вы заметите, что я не беспокоюсь (настолько) о проблемах с перекрестными потоками при публикации завершенных и текущих событий.
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();
}
Я надеюсь, что это поможет понять несколько возможных способов реализации того, чего вы хотели бы достичь.
-Нико
person
Nico
schedule
08.04.2015
downloader
, что не разрешено. Как вы справляетесь с этим? вы подавляете его, устанавливаяthis.CheckForIllegalCrossThreadCalls = false
? - person kennyzx   schedule 08.04.2015.Abort()
в темы — это опасно. - person Enigmativity   schedule 08.04.2015