【问题标题】:Updating a listbox from worker thread?从工作线程更新列表框?
【发布时间】:2016-05-21 20:41:57
【问题描述】:

我在这个主题上找到了article,但我不知道如何实施所提出的答案。

我得到的是一个跨线程异常,我意识到 GUI 在一个线程中,而工作人员在另一个线程中。例外:

System.InvalidOperationException was unhandled by user code
  HResult=-2146233079
  Message=Cross-thread operation not valid: Control 'listBoxCodes' accessed from a thread other than the thread it was created on.
  Source=System.Windows.Forms
  StackTrace:
       at System.Windows.Forms.Control.get_Handle()
       at System.Windows.Forms.Control.SendMessage(Int32 msg, Int32 wparam, String lparam)
       at System.Windows.Forms.ListBox.NativeInsert(Int32 index, Object item)
       at System.Windows.Forms.ListBox.ObjectCollection.AddInternal(Object item)
       at System.Windows.Forms.ListBox.ObjectCollection.Add(Object item)
       at Find_Duplicate_MX_codes.MyThread.backgroundWorker_DoWork(Object sender, DoWorkEventArgs e) in D:\My Programs\Find Duplicate MX codes\Find Duplicate MX codes\MyThread.cs:line 69
       at System.ComponentModel.BackgroundWorker.OnDoWork(DoWorkEventArgs e)
       at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object argument)
  InnerException: 

现在,我最初试图通过编写如下代码来解决这个问题:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Find_Duplicate_MX_codes
{
    public struct ThreadSettings
    {
        public Find_Duplicate_MX_codes.Form1 pMainForm { get; set; }
        public ProgressBar progressBar { get; set; }
        public TextBox progressLabel { get; set; }
        public String strFile { get; set; }
        public ListBox lbCodes { get; set; }
        public ListBox lbCodesDuplicate { get; set; }
    }

    class MyThread
    {
        private BackgroundWorker m_backgroundWorker;
        ThreadSettings m_sThreadSettings;

        public MyThread(ThreadSettings sThreadSettings)
        {
            m_sThreadSettings = sThreadSettings;

            m_backgroundWorker = new BackgroundWorker();
            m_backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
            m_backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
            m_backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
            m_backgroundWorker.WorkerReportsProgress = true;
        }

        public void start()
        {
            if(m_sThreadSettings.pMainForm != null)
                m_sThreadSettings.pMainForm.Enabled = false; // Stop user interacting with the form
            m_backgroundWorker.RunWorkerAsync();
        }

        public void stop()
        {
            m_backgroundWorker.CancelAsync();
        }

        public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            m_backgroundWorker.ReportProgress(0, "Extracting MX codes {0}%)");

            using (var reader = new StreamReader(m_sThreadSettings.strFile))
            {
                Stream baseStream = reader.BaseStream;
                long length = baseStream.Length;

                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    if (line.Length > 8 && line.Substring(0, 4) == "080,")
                    {
                        string strCode = line.Substring(4, 4);

                        if (m_sThreadSettings.lbCodes.FindStringExact(strCode) == -1)
                        {
                            m_sThreadSettings.lbCodes.Items.Add(strCode);
                        }
                        else
                            m_sThreadSettings.lbCodesDuplicate.Items.Add(strCode);
                    }

                    m_backgroundWorker.ReportProgress(Convert.ToInt32(baseStream.Position / length * 100), "Extracting MX codes {0}%)");
                }
            }
        }

        private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if(m_sThreadSettings.progressBar != null)
                m_sThreadSettings.progressBar.Value = e.ProgressPercentage;
            if(m_sThreadSettings.progressLabel != null)
                m_sThreadSettings.progressLabel.Text = String.Format((string)e.UserState, e.ProgressPercentage);
        }

        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            int iResult = Convert.ToInt32(e.Result);
            if (m_sThreadSettings.pMainForm != null)
                m_sThreadSettings.pMainForm.Enabled = true; // User can interact with form again
        }
    }
}

如果我注释掉试图将项目添加到列表框中的代码行是有效的。进度条/标签正在更新。当我尝试更新失败的列表框时。

在另一篇文章的答案中建议:

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}

但我很难理解如何在我的代码行中使用该答案:

m_sThreadSettings.lbCodes.Items.Add(strCode);

如何更改它以便填充列表框并且:

a) 没有得到跨线程异常 b) 不会因为更新列表框而阻塞 GUI

谢谢!

更新:我试过这段代码:

Invoke(new MethodInvoker(delegate { m_sThreadSettings.lbCodes.Items.Add(strCode); }));

但我收到了我不完全理解的警告:

更新 2:我现在意识到 Invoke 是 Form 对象的一部分。所以我将代码更改为:

if (m_sThreadSettings.lbCodes.FindStringExact(strCode) == -1)
    m_sThreadSettings.pMainForm.Invoke(new MethodInvoker(delegate { m_sThreadSettings.lbCodes.Items.Add(strCode); }));
else
    m_sThreadSettings.pMainForm.Invoke(new MethodInvoker(delegate { m_sThreadSettings.lbCodesDuplicate.Items.Add(strCode); }));

现在看来可以了。但是看这个动画:

https://www.dropbox.com/s/mznoqhqll8m28x7/Results0001.mp4?dl=0

这并没有完全按照我的预期进行。一旦阅读完成,它似乎仍在进行 GUI 的所有处理。困惑。

更新 3:我使用 Console.Beep() 来确定我的进度更改处理程序何时被触发。此外,我确认了进度实际发生变化的时间。除了接近尾声时,它会为 100。所以这个 BaseSteam.Position 可能是错误的。

【问题讨论】:

标签: c# multithreading listbox


【解决方案1】:

原来我的代码存在不相关的问题。

第 1 期

这是从工作线程更新列表框的方法:

public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    m_backgroundWorker.ReportProgress(0, "Extracting MX codes {0}%");

    using (var reader = new StreamReader(m_sThreadSettings.strFile))
    {
        Stream baseStream = reader.BaseStream;
        long length = baseStream.Length;

        string line;
        while ((line = reader.ReadLine()) != null)
        {
            if (line.Length > 8 && line.Substring(0, 4) == "080,")
            {
                string strCode = line.Substring(4, 4);

                if (m_sThreadSettings.lbCodes.FindStringExact(strCode) == -1)
                    m_sThreadSettings.pMainForm.Invoke(new MethodInvoker(delegate { m_sThreadSettings.lbCodes.Items.Add(strCode); }));
                else
                    m_sThreadSettings.pMainForm.Invoke(new MethodInvoker(delegate { m_sThreadSettings.lbCodesDuplicate.Items.Add(strCode); }));
            }

            m_backgroundWorker.ReportProgress(Convert.ToInt32((100 / (double)length) * baseStream.Position), "Extracting MX codes {0}%");
        }
    }
}

您必须使用表单的 Invoke 方法。

第 2 期

需要调整计算进度的逻辑,因为在这种情况下,由于计算的工作方式,结果始终为 0。所以我现在使用:

m_backgroundWorker.ReportProgress(
      Convert.ToInt32((100 / (double)length) * baseStream.Position),
      "Extracting MX codes {0}%");

瞧!我的表单列表框内容正在更新,进度条正在按预期更新...... :)

【讨论】:

    猜你喜欢
    • 2014-09-22
    • 1970-01-01
    • 1970-01-01
    • 2011-01-07
    • 1970-01-01
    • 1970-01-01
    • 2015-05-10
    • 2018-03-29
    • 2017-07-05
    相关资源
    最近更新 更多