繁体   English   中英

从另一个线程更新列表框后,如何强制主GUI线程更新列表框?

[英]How do I force the main GUI thread to update a listbox after it is updated from a different thread?

我正在使用生产者/消费者队列构造来修补多线程下载器。 下载部分工作正常,但是在更新GUI时遇到了问题。

现在,我在窗体上使用列表框控件来显示状态消息和下载进度的更新,最终我希望将其替换为进度条。

首先是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线程上的列表框进度如何; 这可以工作,但是列表框实际上不会更新,直到所有步骤完成,并且在“全部完成!”之后还会显示消息。 消息,这是不可取的。

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; 
    }
}

}

有什么建议,例子吗? 我一直在寻找解决方案,但是找不到我可以遵循或工作的任何东西。

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

来源:Producer / Consumer示例: http : //www.albahari.com/threading/part4.aspx

更新:我正在以错误的方式进行操作; 而不是从工作线程调用回GUI,我将使用GUI线程必须注意的事件。 一个教训。 :)

您应该消除 FindOpenForm并将一个ProgressChanged事件添加到TaskQueue 直接调用表示层(窗体或控件) 绝对不是 TaskQueue的责任。 然后,表单的职责是侦听任务生成的“进度更改”事件,然后适当地更新自身。

这将很容易解决您的问题,将保持简单,遵循最佳实践,并消除时间问题,线程问题等。

我的其他答案更合适。 一旦更改TaskQueue以引发ProgressChanged事件,此答案就更合适。


尝试调用listboxProgressMessages.Refresh() 这迫使油漆。 检查Control.Refresh文档。 有时,您必须调用表单的refresh方法。

控制刷新方法

强制控件使它的客户区无效,并立即重绘自身和所有子控件。

http://msdn.microsoft.com/en-us/library/system.windows.forms.control.refresh.aspx

另外,在你的代码,而不是调用BeginInvoke ,调用Invoke -后者是从调用者同步。 这意味着您将调用GUI,并等到GUI完成其操作后再返回。

而且,GUI线程可能会饿死,我认为对GUI线程的控件调用实际上已编组到消息泵上-如果泵不泵送,则调用将永远无法通过。

更新:或者,您可以使用BackgroundWorker表单组件。 它已构建为可在WinForms中使用,从而可与GUI一起使用。 它公开一个ProgressChanged事件,您可以使用该事件将任意进度报告传递回UI。 事件本身被编组,以便它自动调用GUI线程。 这消除了您手动进行操作的需要。

BackgroundWorker线程也使用线程池。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM