我正在使用WebClient对象的DownloadData从几个网站下载favicon。
我收到Byte数组的响应,一切都运行良好,除了一件事:当 DownloadData 方法执行时,它会冻结我的表单直到方法返回
现在,我已经通过使用BackgroundWorker对象来解决这个问题,以完成工作,但我很好奇我将如何使用 System.Threading.Thread实现相同的功能
我尝试创建另一个下载了favicons的Thread,然后循环我的mainThread,直到Thread完成处理,然后使用Abort()方法中止线程,但到目前为止我的Form在执行另一个时被冻结线程。
这是我用来创建另一个Thread的代码:
bool downloadFavIcon_Completed = false;
private void downloadFavIcon()
{
downloadFavIcon_Completed = false;
Byte[] dl;
System.IO.MemoryStream dlMem;
Bitmap favCollection = new Bitmap(96, 64);
Graphics g = Graphics.FromImage(favCollection);
Bitmap dlImg;
String[] addr = new String[24];
addr[0] = @"http://google.com/favicon.ico";
addr[1] = @"http://microsoft.com/favicon.ico";
addr[2] = @"http://freesfx.com/favicon.ico";
addr[3] = @"http://yahoo.com/favicon.ico";
addr[4] = @"http://downloadha.com/favicon.ico";
addr[5] = @"http://hp.com/favicon.ico";
addr[6] = @"http://bing.com/favicon.ico";
addr[7] = @"http://webassign.com/favicon.ico";
addr[8] = @"http://youtube.com/favicon.ico";
addr[9] = @"https://twitter.com/favicon.ico";
addr[10] = @"http://cc.com/favicon.ico";
addr[11] = @"http://stackoverflow.com/favicon.ico";
addr[12] = @"http://vb6.us/favicon.ico";
addr[13] = @"http://facebook.com/favicon.ico";
addr[14] = @"http://flickr.com/favicon.ico";
addr[15] = @"http://linkedin.com/favicon.ico";
addr[16] = @"http://blogger.com/favicon.ico";
addr[17] = @"http://blogfa.com/favicon.ico";
addr[18] = @"http://metal-archives.com/favicon.ico";
addr[19] = @"http://wordpress.com/favicon.ico";
addr[20] = @"http://metallica.com/favicon.ico";
addr[21] = @"http://wikipedia.org/favicon.ico";
addr[22] = @"http://visualstudio.com/favicon.ico";
addr[23] = @"http://evernote.com/favicon.ico";
for (int i = 0; i < addr.Length; i++)
{
using (System.Net.WebClient client = new System.Net.WebClient())
{
try
{
dl = client.DownloadData(addr[i]);
dlMem = new System.IO.MemoryStream(dl);
dlImg = new Bitmap(dlMem);
}
catch (Exception)
{
dlImg = new Bitmap(Properties.Resources.defaultFavIcon);
}
}
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
passAddDisplay.Image = favCollection;
downloadFavIcon_Completed = true;
}
private void button2_Click(object sender, EventArgs e)
{
Thread downloader = new Thread(new ThreadStart(downloadFavIcon));
downloader.Start();
while (!downloader.IsAlive) ;
while (!downloadFavIcon_Completed) ;
downloader.Abort();
}
注意: passAddDisplay 是已放置在我表单上的pictureBox。
如何改进我的应用程序以避免在执行WebClient.DownloadData期间被冻结? (我不想使用Application.DoEvents())
答案 0 :(得分:1)
是的,它是一个同步方法,它导致线程在没有处理消息的情况下等待,直到它返回为止;你应该尝试它的Async版本,它没有。
答案 1 :(得分:0)
欢迎使用Stack Overflow ...
通过检查循环,很明显你是在循环中锁定UI线程。
while (!downloader.IsAlive) ;
while (!downloadFavIcon_Completed) ;
您的UI锁定的原因。理想情况下,这将是后台工作者的工作,因为它被设计为在后台运行并提供事件以回溯到您的UI线程。这可以使用Thread写入,但是应该使用后台工作程序。
现在,如果您真的想使用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事件定义了2个委托。这些代理接受下载器实例作为底部列出的发送方和特殊事件arg类。接下来是我们的3个事件(如上所列),这些事件将用于指示下载器的更改。
接下来我们定义了字段。
Thread thread;
这是对调用&#39; 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();
}
我们要做的第一件事就是检查是否设置了中止标志。如果是,则忽略启动调用(您可以抛出异常)。接下来,我们检查线程是否为空。如果线程不为null,则我们的下载器正在运行。最后,我们只需将我们的中止标记重置为false
并启动新的Thread
。
向下移动到Abort()
方法。此方法将首先检查是否设置了aborting
标志。如果是这样,那就什么也不做。接下来,我们将aborting
标志设置为true。下一步,我将警告你, WILL 阻止你的调用线程调用方法Thread.Join()
,它将把线程加入我们的调用线程。基本上等待线程退出。
最后,我们只需将线程实例设置为null,将aborting
标志重置为false并触发Cancelled
事件(如果已订阅)。
接下来是下载的主要方法。首先,您会注意到我移动了变量,并使用using
语句作为一次性对象。 (这是另一个话题)。
runDownloader()
方法的一大特色就是它会定期检查“中止”。旗。如果此标记设置为true
,那么downloader
就会停止。现在请注意,在WebClient
下载图像时,可能会出现中止的情况。理想情况下,您可以让WebClient
完成请求,正确处理然后退出循环。
每次下载图像后,都会触发progress事件(如果已订阅)。最后,当迭代完成并且所有图像都下载了&#34;已完成&#34;使用编译的位图图像触发事件。
喘息机的暂停
现在这一切都很棒..但你如何使用它。很简单,我创建了一个带有按钮,进度条和图片框的表单。该按钮将用于启动和停止下载程序,进度条用于处理进度事件以及完成图像的图片框。
以下是我对其中部分内容进行评论的示例程序。
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
对象来完成工作的简单实现。在我看来,这是太多的工作。现在让我们在后台工作者中进行。
我强烈推荐这种方法
你为什么这么说?那么后台工作者为我们提供了我们试图实现的测试和支持的方法(以及更多)。您会注意到下载图像和检测取消的实际工作是相对相同的。但是,您会注意到在发布已完成和进度事件时,我并不担心(尽可能多)关于跨线程问题。
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();
}
我希望这有助于理解一些可能的方法来实现您想要实现的目标。
-Nico
答案 2 :(得分:0)
我会考虑使用微软的Reactive Framework。它自动处理背景线程,并将非常有效地清理所有一次性参考。 NuGet“Rx-Main”&amp; “RX-的WinForms”/ “RX-WPF”。
首先,从您的地址数组开始:
var addr = new []
{
"http://google.com/favicon.ico",
// DELETED FOR BREVITY
"http://evernote.com/favicon.ico",
};
现在,定义一个异步获取图像的查询:
var query =
from a in addr.ToObservable().Select((url, i) => new { url, i })
from dl in Observable
.Using(
() => new System.Net.WebClient(),
wc => Observable.FromAsync(() => wc.DownloadDataTaskAsync(a.url)))
from bitmap in Observable
.Using(
() => new System.IO.MemoryStream(dl),
ms => Observable.Start(() => new Bitmap(ms)))
.Catch(ex => Observable.Return(new Bitmap(Properties.Resources.defaultFavIcon)))
select new { x = (a.i % 6) * 16, y = (a.i / 6) * 16, bitmap };
最后,等待所有图像进入,然后在UI线程上创建合成图像并将其分配给passAddDisplay
控件。
query
.ToArray()
.ObserveOn(passAddDisplay)
.Subscribe(images =>
{
var favCollection = new Bitmap(96, 64);
using(var g = Graphics.FromImage(favCollection))
{
foreach (var image in images)
{
g.DrawImage(image.bitmap, image.x, image.y, 16, 16);
image.bitmap.Dispose();
}
}
passAddDisplay.Image = favCollection;
});
我测试了查询,它运行正常。