近来需要写一个文件下载模块,用到多线程来显示进度条,后台线程进行文件下载,并通过委托更新主界面的进度条显示,但是发现会出现主界面卡主的情况,问题是否是由于进度条绘制(更新)是有主界面去绘制?我看到Application.DoEvents()并不会出现卡主界面的情况,有些迷惑,下面是我的代码,请各位帮忙看看啦:
public Form1()
        {
            InitializeComponent();
            m_fileDownloader = new FileDownloader();
            m_fileDownloader.FireOnShowProgress += new FileDownloader.ShowProgress(ShowProgress);
            m_fileDownloader.FireOnSize += new FileDownloader.GetFileSize(fireOnFileSize);
        }        /// <summary>
        /// 回调,用于处理进度条的最大值
        /// </summary>
        private void fireOnFileSize(long size)
        {
            if(InvokeRequired)
            {
                BeginInvoke(new FileDownloader.GetFileSize(fireOnFileSize), new Object[]{size});
                return;
            }
            progressBar1.Maximum = (int)size;
            progressBar1.Invalidate();
        }        /// <summary>
        /// 回调,用于显示进度条
        /// </summary>
        private void ShowProgress(long npos)
        {
            if(InvokeRequired)
            {
                BeginInvoke(new FileDownloader.ShowProgress(ShowProgress), new Object[] {npos});
                return;
            }            progressBar1.Value = (int)npos;
            if(npos == progressBar1.Maximum)
            {
                progressBar1.Visible = false;
                MessageBox.Show("下载完成");
            }
        }        /// <summary>
        /// 开始下载文件
        /// </summary>
        private void btnDownload_Click(object sender, EventArgs e)
        {
            m_fileDownloader.DownLoadFile("http://a.de-zhou.com/vip/lll/左边.mp3");
        }    public class FileDownloader
    {
        private string m_strUrl;        public delegate void GetFileSize(long size);
        public delegate void ShowProgress(long pos);        /// <summary>
        /// 用于处理文件的总长度
        /// </summary>
        public event GetFileSize FireOnSize;
        /// <summary>
        /// 用于处理文件的下载进度
        /// </summary>
        public event ShowProgress FireOnShowProgress;        /// <summary>
        /// 下载文件
        /// </summary>
        /// <param name="strUrl">下载地址</param>
        public void DownLoadFile(string strUrl)
        {
            m_strUrl = strUrl;
            Thread thread = new Thread(new ThreadStart(DownloadThread));
            thread.Start();
        }        /// <summary>
        /// 下载线程
        /// </summary>
        private void DownloadThread()
        {
            HttpWebRequest Myrq = (HttpWebRequest)HttpWebRequest.Create(m_strUrl);
            HttpWebResponse Myrp = (HttpWebResponse)Myrq.GetResponse();
            long totalBytes = Myrp.ContentLength;
            if (FireOnSize != null)
                FireOnSize(totalBytes);            Stream st;
            try
            {
                st = Myrp.GetResponseStream();
            }
            catch (System.Exception e)
            {
                return;
            }
                       string strFileName  = "";
            int npos = m_strUrl.LastIndexOf("/");
            if(npos != -1)
                strFileName = m_strUrl.Substring(npos +1, m_strUrl.Length - npos -1);
            string strPath = "\\Storage Card\\" + strFileName;
            Stream so = new FileStream(strPath, FileMode.Create);            long totalDownloadedByte = 0;
            byte[] by = new byte[1024];
            int osize = st.Read(by, 0, (int)by.Length);
            while (osize > 0)
            {
                totalDownloadedByte = osize + totalDownloadedByte;
                so.Write(by, 0, osize);                if (FireOnShowProgress != null)
                    FireOnShowProgress(totalDownloadedByte);                osize = st.Read(by, 0, (int)by.Length);
            }
            so.Close();
            st.Close();
        }
    }

解决方案 »

  1.   

    试试这里加上Application.DoEvents(),不过我也不敢肯定这样就能好,因为你的进度条更新太频繁了,你在下载的时候,每完成1024byte就需要更新一次进度条,如果网络下载速度是100KB/S的话,差不多是每0.01毫秒就更新一次进度条,那样界面的确是看似卡死了,毕竟进度条的重绘也需要时间的。        private void ShowProgress(long npos)
            {
                if(InvokeRequired)
                {
                    BeginInvoke(new FileDownloader.ShowProgress(ShowProgress), new Object[] {npos});
                    return;
                }
                Application.DoEvents();
                progressBar1.Value = (int)npos;
                if(npos == progressBar1.Maximum)
                {
                    progressBar1.Visible = false;
                    MessageBox.Show("下载完成");
                }
            }
      

  2.   

    这么不用backgroundworker组件呢 把下载的代码放到dowork方法里
    把更新界面的放到完成事件里,
    backgroundworker天生就是做这个的
      

  3.   

    楼主如果是网页程序,建议用AJAX和JS来实现,书籍通过AJAX去取,滚动条通过JS去控制DIV在表格中填充的方式去构建滚动条,这样的方式简单实用,而且滚动条可以自定义样式,至少绝对可以防止进度条界面卡死,如果是WINFORM程序,采用代理异步回调,应该也可以防止界面卡死
      

  4.   

    如果采用Application.DoEvents()的方式的话,我试过是不会出现主界面卡住的情况,但是我也不必另外开一个线程来进行后台下载了。
    根据txg92的说法,那我上面那个事采用异步回调,照理也不会出现界面卡主的情况啊?是否连那个绘制进度条的任务也应该交给后台线程操作?不知道是否有更好的方法呢?
      

  5.   

    进度条更新太频繁了,确实看起来界面象卡住一样,用DoEvents()也不是办法,这是VS6下的,到.NET平台作用不太。最好还是改成BackgroundWorker组件,其中提供显示进度的事件,这个好用。
      

  6.   

    BackgroundWorker在.net campact下面无法使用,DoEvents考虑到是在手机上运行该程序,故还是不行,现在的初步的方案是采用异步委托的方式来绘制主界面的进度条,不知道各位还有没有其他的好方法?
      

  7.   

    你这个问题用BackgroundWorker一样还是会出现这种效果,BackgroundWorker也是多线程的一种,你还是没有找到问题的关键,你锁死界面不是因为用的方法问题,而是你更新太频繁了,这是我最后一次强调了,你再听不进去我就不管了,问题的方向都没有找到,怎么可能解决问题。想想平时看到的进度条一般都是1秒才刷新一次显示,而你1秒至少刷新10万次显示