欢迎来到 Stack Overflow...
通过检查您的循环,很明显您将 UI 线程锁定在循环中。
while (!downloader.IsAlive) ;
while (!downloadFavIcon_Completed) ;
这是您的 UI 锁定的原因。理想情况下,这将是后台工作人员的工作,因为它被设计为在后台运行并提供事件以返回到您的 UI 线程。这可以使用线程编写,但应该使用后台工作人员。
现在,如果您真的想使用Thread 对象编写此代码,那么我建议您为下载器创建一个类并创建事件。因此我们可以编写简单的事件,例如
- 活动结束
- 活动取消
- UI 进度事件
我不推荐这种方法(进一步阅读)
首先创建一个新类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 事件定义了 2 个委托。这些委托接受下载器实例作为底部列出的发送者和特殊事件 arg 类。接下来是我们的 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();
}
我们要做的第一件事是检查是否设置了中止标志。如果是则忽略开始调用(您可以抛出异常)。接下来我们检查线程是否不为空。如果线程不为空,那么我们的下载器正在运行。最后,我们只需将我们的中止标志重置为false 并启动我们的新Thread。
向下移动到Abort() 方法。此方法将首先检查是否设置了aborting 标志。如果是这样,那么什么都不做。接下来我们将aborting 标志设置为true。下一步,我会警告你这个 WILL 阻塞你的调用线程是调用方法Thread.Join(),它将把线程加入我们的调用线程。基本上是等待线程退出。
最后,我们只需将线程实例设置为 null,将 aborting 标志重置为 false 并触发 Cancelled 事件(如果已订阅)。
接下来是进行下载的主要方法。首先,您会注意到我移动了您的变量并使用using 声明一次性对象。 (那是另一个话题)。
runDownloader() 方法的重要部分是它定期检查“中止”标志。如果此标志设置为true,downloader 将停在那里。现在请注意,您可能会遇到在 WebClient 正在下载图像时调用 abort 的情况。理想情况下,您会让WebClient 完成请求,正确处理然后退出循环。
每次下载图像后,都会触发进度事件(如果已订阅)。最后,当迭代完成并下载所有图像时,“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 为我们提供了我们尝试实现的 tested 和 supported 方法(以及更多)。您会注意到下载图像和检测取消的实际工作是相对相同的。但是您会注意到,在发布完成和进度事件时,我并不担心(同样多)跨线程问题。
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();
}
我希望这有助于了解实现您想要实现的目标的几种可能方法。
-妮可