C# 如何在列表框从其他线程更新后强制主GUI线程更新列表框?

C# 如何在列表框从其他线程更新后强制主GUI线程更新列表框?,c#,winforms,multithreading,user-interface,C#,Winforms,Multithreading,User Interface,我正在修补一个多线程下载程序,使用生产者/消费者队列结构;下载部分工作正常,但我在保持GUI更新时遇到了问题 现在,我在表单上使用listbox控件来显示状态消息和下载进度的更新,最终我希望用progressbars替换它 首先是后面的Form1代码;表单只包含一个按钮和列表框: public partial class Form1 : Form { public Form1() { InitializeComponent(); } public

我正在修补一个多线程下载程序,使用生产者/消费者队列结构;下载部分工作正常,但我在保持GUI更新时遇到了问题

现在,我在表单上使用listbox控件来显示状态消息和下载进度的更新,最终我希望用progressbars替换它

首先是后面的Form1代码;表单只包含一个按钮和列表框:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    public void SetProgressMessage(string message) 
    { 
        if (listboxProgressMessages.InvokeRequired) 
        {
            listboxProgressMessages.Invoke(new MethodInvoker(delegate()
            { SetProgressMessage(message); })); 
        } 
        else 
        {
            listboxProgressMessages.Items.Add(message);
            listboxProgressMessages.Update();
        } 
    }

    private void buttonDownload_Click(object sender, EventArgs e)
    {
        SetProgressMessage("Enqueueing tasks");
        using (TaskQueue q = new TaskQueue(4))
        {
            q.EnqueueTask("url");
            q.EnqueueTask("url");
            q.EnqueueTask("url");
            q.EnqueueTask("url");
            q.EnqueueTask("url");
            q.EnqueueTask("url");
            q.EnqueueTask("url");
            q.EnqueueTask("url");
            q.EnqueueTask("url");
            q.EnqueueTask("url");
        }
        SetProgressMessage("All done!");
    }
}
现在是生产者/消费者逻辑。消费者一点一点地下载文件,并且应该告诉GUI线程上的listbox进度如何;这是可行的,但是列表框直到全部完成才真正更新,消息也会出现在“全部完成”之后信息,这是不可取的

TaskQueue.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using System.Net;
using System.Windows.Forms;
using System.Runtime.Remoting.Messaging;

namespace WinformProducerConsumer
{
    public class TaskQueue : IDisposable
    {
        object queuelocker = new object();
        Thread[] workers;
        Queue<string> taskQ = new Queue<string>();

    public TaskQueue(int workerCount)
    {
        workers = new Thread[workerCount];
        for (int i = 0; i < workerCount; i++)
            (workers[i] = new Thread(Consume)).Start();
    }

    public void Dispose()
    {
        foreach (Thread worker in workers) EnqueueTask(null);
        foreach (Thread worker in workers) worker.Join();
    }

    public void EnqueueTask(string task)
    {
        lock (queuelocker)
        {
            taskQ.Enqueue(task);
            Monitor.PulseAll(queuelocker);
        }
    }

    void Consume()
    {
        while (true)
        {
            string task;
            Random random = new Random(1);
            lock (queuelocker)
            {
                while (taskQ.Count == 0) Monitor.Wait(queuelocker);
                task = taskQ.Dequeue();
            }
            if (task == null) return;

            try
            {
                Uri url = new Uri(task);
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                response.Close();
                Int64 iSize = response.ContentLength;
                Int64 iRunningByteTotal = 0;

                using (WebClient client = new System.Net.WebClient())
                {
                    using (Stream streamRemote = client.OpenRead(new Uri(task)))
                    {
                        using (Stream streamLocal = new FileStream(@"images\" + Path.GetFileName(task), FileMode.Create, FileAccess.Write, FileShare.None))
                        {
                            int iByteSize = 0;
                            byte[] byteBuffer = new byte[iSize];
                            while ((iByteSize = streamRemote.Read(byteBuffer, 0, byteBuffer.Length)) > 0)
                            {
                                streamLocal.Write(byteBuffer, 0, iByteSize);
                                iRunningByteTotal += iByteSize;

                                double dIndex = (double)iRunningByteTotal;
                                double dTotal = (double)byteBuffer.Length;
                                double dProgressPercentage = (dIndex / dTotal);
                                int iProgressPercentage = (int)(dProgressPercentage * 100);

                                string message = String.Format("Thread: {0} Done: {1}% File: {2}",
                                    Thread.CurrentThread.ManagedThreadId,
                                    iProgressPercentage,
                                    task);

                                Form1 frm1 = (Form1)FindOpenForm(typeof(Form1));
                                frm1.BeginInvoke(new MethodInvoker(delegate()
                                {
                                    frm1.SetProgressMessage(message);
                                })); 
                            }
                            streamLocal.Close();
                        }
                        streamRemote.Close();
                    }
                }

            }
            catch (Exception ex)
            {
                // Generate message for user
            }
        }
    }

    private static Form FindOpenForm(Type typ) 
    { 
        for (int i1 = 0; i1 < Application.OpenForms.Count; i1++) 
        { 
            if (!Application.OpenForms[i1].IsDisposed && (Application.OpenForms[i1].GetType() == typ))
            { 
                return Application.OpenForms[i1]; 
            } 
        } 
        return null; 
    }
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
使用系统文本;
使用系统线程;
使用System.IO;
Net系统;
使用System.Windows.Forms;
使用System.Runtime.Remoting.Messaging;
命名空间WinformProducerConsumer
{
公共类任务队列:IDisposable
{
object queuelocker=新对象();
工作人员;
Queue taskQ=新队列();
公共任务队列(int workerCount)
{
工人=新线程[工人计数];
对于(int i=0;i0)
{
streamLocal.Write(byteBuffer,0,iByteSize);
iRunningByteTotal+=iByteSize;
double dIndex=(double)iRunningByteTotal;
double dTotal=(double)byteBuffer.Length;
double dProgressPercentage=(dIndex/dTotal);
int iProgressPercentage=(int)(dProgressPercentage*100);
string message=string.Format(“线程:{0}完成:{1}%文件:{2}”,
Thread.CurrentThread.ManagedThreadId,
iProgressPercentage,
任务);
Form1 frm1=(Form1)FindOpenForm(typeof(Form1));
frm1.BeginInvoke(新方法调用程序(委托)()
{
frm1.SetProgressMessage(消息);
})); 
}
streamLocal.Close();
}
streamRemote.Close();
}
}
}
捕获(例外情况除外)
{
//为用户生成消息
}
}
}
私有静态表单FindOpenForm(类型typ)
{ 
for(int i1=0;i1
}

有什么建议、例子吗?我四处寻找解决方案,但找不到任何可以遵循或有效的方法

将frm1.BeginInvoke(new MethodInvoker(delegate())替换为frm1.Invoke(new MethodInvoker(delegate())会导致死锁。我在这里很困惑

资料来源: 生产者/消费者示例:


更新:我的做法是错误的;我将使用GUI线程必须关注的事件,而不是从工作线程调用回GUI。吸取的教训是:

我的另一个答案更合适。一旦更改TaskQueue以引发ProgressChanged事件,这个答案就更合适了


尝试调用
listboxProgressMessages.Refresh()
。这将强制绘制。请检查
控件。Refresh
文档。有时,您必须调用表单的Refresh方法

Control.Refresh方法

强制控件使其属性无效 客户区域并立即重新绘制 自身和任何子控件

也在您的代码中