有已知的远程图片URL列表数组(20-30个图片地址),现需要将其下载到本地服务器上,并将保存的图片路径存入数据库中。我现在是单个单个的读取下载,再将其路径保存到数据库.这种方式很慢,在网上看到说多线程、异步下载速度快,由于还不会这技术,现求示例代码

解决方案 »

  1.   

    WebClient.DownloadFile()
    本身支持异步。
      

  2.   

    简单来说,类似这样的代码: 假设你的原始的url写到一个叫做yourUrls的数组中,并且写好了一个方法string 下载一个文件(string url){
      ....
    }它下载一个url文件到本地,然后返回本地保存路径,那么多线程下载并等待所有下载完成的代码就是:var results = yourUrls.AsParallel().Select(url => 下载一个文件(url)).ToArray();
    这样得到了一个本地文件路径数组,就是所有并行下载的结果。当然,这是基于.net4.0上的Linq的。
      

  3.   


    但我用的是.net2.0啊,怎么办呢?
      

  4.   


    假设你不需要同步等待所有的下载结束,也就是说写一个这样的方法public void 下载一个文件(object url)
    {
       ....
    }它下载url并且写入数据库,你只要让他们去并行下载就可以了,不需要等待、监听何时下载完成,那么简单地这样写代码for (int i = 0; i < yourUrls.Length; i++)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(下载一个文件), yourUrls[i]);
    }
      

  5.   

    在你的.net2.0下,要等待线程全都结束,然后继续进行后边处理,其实要比使用.net4.0麻烦不少。首先现在的任务复杂了,简单地一个“下载一个文件”方法根本不能处理,因为它需要本地路径,还要懂得相互协同,所以需要增加两个属性,这样我们可以把一个方法“重构”为一个class:public class 下载任务
    {
        public string result = null;
        public AutoResetEvent gate;   //用来同步阻塞主线程    public void 下载一个文件(object url)
        {
            Console.WriteLine("计算{0}", url);
            Thread.Sleep(10);
            //......你的下载代码,根据this.url下载文件,并且将生成的本地文件路径写入result
            result = string.Format("计算{0}的结果", url);
            gate.Set();
        }
    }然后你的主线程需要保存多个任务,以便将来可以遍历其result返回值。同时也要初始化一个AutoResetEvent机制来同步所有任务:AutoResetEvent gate = new AutoResetEvent(false);
    下载任务[] tasks = new 下载任务[yourUrls.Length];
    for (int i = 0; i < yourUrls.Length; i++)
    {
        下载任务 task = new 下载任务();
        tasks[i] = task;
        task.gate = gate;
        ThreadPool.QueueUserWorkItem(new WaitCallback(task.下载一个文件), yourUrls[i]);
    }
    for (int i = 0; i < tasks.Length; i++)
        gate.WaitOne();
    for (int i = 0; i < tasks.Length; i++)
        现在可以遍历tasks[i].result查看每一个本地文件路径;关于AutoResetEvent你自己去看msdn吧。不过设计多线程的程序,需要很多经验,而且真正的难点是当你的程序大了之后很难以测试和调试。最好是使用.net的并行Linq等简单的方法来开发。
      

  6.   

    我在上面把“下载一个文件”方法重构为一个class中的方法,写了3行示例代码说明如何使用result和gate属性。你应该修改为你自己的下载程序来修改前3行代码。因为要交给系统线程池其并发执行这个方法,所以这个方法的参数定义上只能有一个object类型的参数。因此使用这个class,才能做到为“下载一个文件”方法增加了是两个参数的目的。
      

  7.   

    非常感谢sp1234的热心帮助,我刚才调试了下您的代码,没成功,现将全部测试代码发上来,帮帮忙再看看是咱回事呢?
    test.cs
    using System;
    using System.Data;
    using System.Configuration;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.HtmlControls;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Threading;
    using System.Collections.Generic;
    using System.Net;
    using System.IO;
    using System.Text;/// <summary>
    ///test 的摘要说明
    /// </summary>
    public class test
    {
    public test()
    {
    //
    //TODO: 在此处添加构造函数逻辑
    //
    }
        public string result = null;
        public AutoResetEvent gatesss;   //用来同步阻塞主线程
        public void DownImgUrl(object url)
        {        string uriString = url.ToString();
            Uri address = new Uri(uriString);
            string fileName = "/test/a" + DateTime.Now.ToString("yyyyMMddHHmmssfff") + ".jpg";        WebClient wc = new WebClient();
            try
            {
                wc.DownloadFile(address, HttpContext.Current.Server.MapPath(fileName));
            }
            catch (Exception exc)
            {
            }
            finally
            {
                if (wc != null) wc.Dispose();
            }        result = string.Format("计算{0}的结果", url);
            gatesss.Set();
        }
    }test.aspx.cs
    protected void Page_Load(object sender, EventArgs e)
        {
            string strHTML = "http://hiphotos.baidu.com/lazylazycat/pic/item/038ccf1310818a5b203f2e7f.jpg#http://hiphotos.baidu.com/396058026/pic/item/f5ccf13b864f32af9e3d62d0.jpg";
            string[] imgurlAry = strHTML.Split('#');
            AutoResetEvent gate = new AutoResetEvent(false);
            //下载任务[] tasks = new 下载任务[yourUrls.Length];
            test[] tasks = new test[imgurlAry.Length];
            for (int i = 0; i < imgurlAry.Length; i++)
            {
                test task = new test();
                tasks[i] = task;
                task.gatesss = gate;
                ThreadPool.QueueUserWorkItem(new WaitCallback(task.DownImgUrl),imgurlAry[i]);
            }
            for (int i = 0; i < tasks.Length; i++)
                gate.WaitOne();
            for (int i = 0; i < tasks.Length; i++)
            {
                //现在可以遍历tasks[i].result查看每一个本地文件路径;
                Response.Write(tasks[i].result+"<br>");
            }