2015-04-08 38 views
1

我正在使用WebClient对象的DownloadData从两个网站下载favicon。WebClient DownloadData方法冻结表格

我收到一个字节数组的响应,一切运作顺利,除了一两件事:DownloadData方法被执行时,它会冻结我的形式,直到该方法返回

现在,我以一个BackgroundWorker对象来完成这项工作解决了这个,但我很好奇我怎么会实现使用System.Threading.Thread同样的事情。

我试着创建另一个线程,下载favicons,然后循环我的mainThread,直到线程完成处理,然后使用Abort()方法中止线程,但到目前为止我的表单在执行期间被冻结其他线程。

这是我用来创建其他线程的代码:

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已经放在我的窗体上的图片框。

如何改进我的应用程序以避免在执行WebClient.DownloadData期间被冻结? (我不想使用Application.DoEvents())

+0

应该存在运行时错误,因为您正在从后台线程'downloader'访问UI元素,这是不允许的。你如何处理这个问题?你是否通过设置'this.CheckForIllegalCrossThreadCalls = false'来压制它? – kennyzx

+0

什么UI元素?你在谈论passAddDisplay吗? –

+0

是的,我的意思是Picturebox。 – kennyzx

回答

0

欢迎堆栈溢出...

通过检查你的循环很明显,可以有效地防止循环语句中的UI线程。

while (!downloader.IsAlive) ; 
while (!downloadFavIcon_Completed) ; 

是您的UI锁定的原因。理想情况下,这对于后台工作人员来说是一份工作,因为它被设计为在后台运行,并提供事件以便返回到UI线程。这可以使用Thread编写,但是应该使用后台工作器。

现在,如果你真的想用Thread对象来写这个,那么我建议你为你的下载器创建一个类并创建事件。因此,我们可以写简单的事件,如

  1. Completed事件
  2. 取消事件
  3. 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个代表。这些代表接受下载器实例作为底部列出的发件人和特殊事件参数类。随后是我们的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。下一步,我会提醒你这个阻止你的调用线程调用方法Thread.Join()这将加入到我们的调用线程的线程。基本上等待线程退出。

最后,我们只需将线程实例设置为空,将aborting标志重置为false并触发Cancelled事件(如果订阅)。

接下来是下载的主要方法。首先你会注意到我移动了你的变量,并使用using语句来处理一次性对象。 (这是另一个话题)。

runDownloader()方法的一大特点是它会定期检查'中止'标志。如果此标志设置为true,则downloader停在此处。现在请注意,您可能会遇到在WebClient下载图像时调用中止的情况。理想情况下,您会让WebClient完成请求,正确处置然后退出循环。

每次下载图像后,都会触发进度事件(如果订阅)。最后,当迭代完成并下载所有图像时,“已完成”事件将与编译后的位图图像一起触发。

停顿了BREATHE

现在,这是所有伟大的..但你如何使用它。简单地说,我使用按钮,进度条和图片框创建了一个表单。该按钮将用于启动和停止下载器,进度条处理进度事件和完成图像的图片框。

这里是我已经评论过它的一部分的示例程序。

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

+0

感谢您花时间详细回答我的问题,我很感激。这真让我感到困惑。 正如我所提到的,我已经通过使用BackgroundWorker解决了它,但我必须说,你比我的完整得多。 我知道现在该怎么做! 再次感谢您。 :) –

1

是的,它是一种同步方法,它使线程在不处理消息的情况下等待,直到它返回;你应该尝试它的异步版本,而不是。

+0

感谢您的回答@nathan –

0

我会考虑使用Microsoft的Reactive Framework。它会自动处理后台线程,并将非常有效地清理所有一次性引用。 NuGet“Rx-Main”&“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; 
    }); 

我测试了查询,它工作正常。