C# 调用 ListView 不起作用(后台线程)

Posted

技术标签:

【中文标题】C# 调用 ListView 不起作用(后台线程)【英文标题】:C# Invoking ListView not working (Background Thread) 【发布时间】:2020-03-10 02:56:21 【问题描述】:

我正在使用 WinForms。 我想从后台线程填充 ListView,但是当我调用 listview 时,我的程序停止并显示错误。错误是“无法访问已处置的对象。对象名称是:ListView。”而当我把这个方法

                lvValidate.Invoke((Action)delegate
                
                    lvValidate.Items.Add(listitem);
                );

在 try-catch 块中,我的程序开始滞后。我不知道问题出在哪里,但我的 Invoke 方法是:

static class Intercept

    internal static void Invoke(this Control control, Action action)
    
        control.Invoke(action);
    

该错误仅在我关闭表单并打开另一个表单(在同一程序中)时显示。在包含 ListView 的表单中,数据不可读,并且似乎加载了数千次。

这就是我的 DoWork,ProgressChanged,RunWorkerCompleted 事件的作用。

        private void bgwLoad_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    
        string commandText = "SELECT * FROM works";
        mysqlCommand command = new MySqlCommand(commandText, connection);
        MySqlDataAdapter da = new MySqlDataAdapter(command);
        connection.Close();
        connection.Open();
        reader = command.ExecuteReader();
        connection.Close();
        DataTable dt = new DataTable();
        da.Fill(dt);

        for (int i = 0; i < dt.Rows.Count; i++)
        
            DataRow dr = dt.Rows[i];
            ListViewItem listitem = new ListViewItem(dr["ID"].ToString(), dr["Date"].ToString());
            listitem.SubItems.Add(dr["Date"].ToString());
            listitem.SubItems.Add(dr["Name"].ToString());
            listitem.SubItems.Add(dr["WorkNumber"].ToString());
            listitem.SubItems.Add(dr["WorkCode"].ToString());
            listitem.SubItems.Add(dr["CoreThread"].ToString());
            listitem.SubItems.Add(dr["Tech"].ToString());
            listitem.SubItems.Add(dr["From"].ToString());
            listitem.SubItems.Add(dr["To"].ToString());
            listitem.SubItems.Add(dr["Validate"].ToString());
            listitem.SubItems.Add(dr["Validate2"].ToString());
            lvValidate.Items.Add(listitem);
        
    

    private void bgwLoad_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
    

    

    private void bgwLoad_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    
        picLoading.Visible = false;
    

【问题讨论】:

显然你已经废弃了 ListView。或者可能只是里面的东西? 不清楚您的显示技术(以及 ListView)是什么:WinForms? WPF? ASP.NET?其他? 一般我在后台使用report progress的方式,将数据发送到ListView所在的主线程。然后在报告进度方法中将数据发送到列表视图。 @Christopher WinForms 是技术 @Christopher 我不知道我处理了什么。代码中没有手动处理任何内容。 【参考方案1】:

尝试以下方法:

    Dispatcher.CurrentDispatcher.Invoke(() => 
       lvValidate.Items.Add(listitem);
    );

编辑:

或者试试这个:

 public static void AddItem(ListItem listitem)
    

        if (lvValidate.InvokeRequired)
        
            AddItemDelegate d = new AddItemDelegate (AddItem);
            lvValidate.Invoke(d, new object[]  listitem );            
        
        else
        
            lvValidate.Invoke(new Action(() =>
            
                lvValidate.Items.Add(listitem);
            ));
                    
     
delegate void AddItemDelegate(ListItem listitem);

然后调用:

AddItem(listitem);

【讨论】:

不起作用。我在 DoWork 活动中使用过。它显示跨线程错误消息:跨线程操作无效:控件'lvValidate'从创建它的线程以外的线程访问。 和以前一样的错误。后台工作人员正在工作,但是当我关闭表单时,程序崩溃并显示问题“无法访问已处置的对象。对象名称为:ListView。” 然后你必须将数据存储在你的调用上下文中,从你打开表单并在每次打开表单时传递它。 你应该在 cunstructor 中设置 ItemSource 我不知道在哪里做这个【参考方案2】:

根据您的 cmets,您正在尝试使用 DataAdapter 从数据库中检索数据,然后将数据零碎地分发。这不起作用有几个原因:

数据适配器

DataAdapter 类的共同点是它们只工作,而 DBConnection 是主动打开的。这就是为什么您会得到“无法访问已处置的对象”的原因。因为当您尝试使用它时,它的连接已经被释放。处置不是你应该拖延的事情。或者根本分手。在 DoWork() 中保持使用(希望得到的)正确的位置。

因此,您始终必须将 DataAdapter 的数据复制到非适配器集合中。真的任何旧清单都可以。这将暂时使内存负载加倍,并且可能会产生一些东西供 GC 清理,但实际上它们是唯一的建议方式。

仅在已完成时批量写入

虽然理论上您可以通过进度报告分发部分读取/处理结果,但不建议尝试。编写 GUI 需要大量开销。我第一次也是唯一一次这样做,我最终用写入和绘制操作锁定了我的 GUI 线程。看起来我从来没有做过多任务处理。更新进度条的成本几乎很低,不会真正引起问题。

默认模式是仅在获得所有数据后才覆盖相关数量的数据。

如果您遇到异常或取消,该模式是假定所有数据都是错误的,并且在 UI 上没有。

我喜欢将 BackgroundWorker 称为“多任务/线程训练轮”。他们教你所有这些东西。第一部分,通过分发 akward。第二部分,如果您尝试在无效情况下使用 Result,则实际抛出异常。

你可能收回了太多

也许数据库最常见的错误是尝试检索大量数据,然后在客户端中进行处理或过滤。一个常见的错误,所以避免它。

对于您定义为 1 页的任何数据,用户可以处理多少数据是有限制的。我的建议是一次不要超过 100 个数据字段。如果您需要过滤、分页或排序,总是在查询中进行。将这些东西移动到客户端会通过网络移动大量不必要的数据,然后处理/过滤的速度就会比数据库慢。

示例代码

这实际上是我的第一个 BGW 项目。多年来我对其进行了一些更新,但大部分内容仍然有效:

#region Primenumbers
private void btnPrimStart_Click(object sender, EventArgs e)

    if (!bgwPrim.IsBusy)
    
        //Prepare ProgressBar and Textbox
        int temp = (int)nudPrim.Value;
        pgbPrim.Maximum = temp;
        tbPrim.Text = "";

        //Start processing
        bgwPrim.RunWorkerAsync(temp);
    


private void btnPrimCancel_Click(object sender, EventArgs e)

    if (bgwPrim.IsBusy)
    
        bgwPrim.CancelAsync();
    


private void bgwPrim_DoWork(object sender, DoWorkEventArgs e)

    int highestToCheck = (int)e.Argument;
    //Get a reference to the BackgroundWorker running this code
    //for Progress Updates and Cancelation checking
    BackgroundWorker thisWorker = (BackgroundWorker)sender;

    //Create the list that stores the results and is returned by DoWork
    List<int> Primes = new List<int>();


    //Check all uneven numbers between 1 and whatever the user choose as upper limit
    for(int PrimeCandidate=1; PrimeCandidate < highestToCheck; PrimeCandidate+=2)
    
        //Report progress
        thisWorker.ReportProgress(PrimeCandidate);
        bool isNoPrime = false;

        //Check if the Cancelation was requested during the last loop
        if (thisWorker.CancellationPending)
        
            //Tell the Backgroundworker you are canceling and exit the for-loop
            e.Cancel = true;
            break;
        

        //Determin if this is a Prime Number
        for (int j = 3; j < PrimeCandidate && !isNoPrime; j += 2)
        
            if (PrimeCandidate % j == 0)
                isNoPrime = true;
        

        if (!isNoPrime)
            Primes.Add(PrimeCandidate);
    

    //Tell the progress bar you are finished
    thisWorker.ReportProgress(highestToCheck);

    //Save Return Value
    e.Result = Primes.ToArray();


private void bgwPrim_ProgressChanged(object sender, ProgressChangedEventArgs e)

    pgbPrim.Value = e.ProgressPercentage;


private void bgwPrim_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)

    pgbPrim.Value = pgbPrim.Maximum;
    this.Refresh();

    if (!e.Cancelled && e.Error == null)
    
        //Show the Result
        int[] Primes = (int[])e.Result;

        StringBuilder sbOutput = new StringBuilder();

        foreach (int Prim in Primes)
        
            sbOutput.Append(Prim.ToString() + Environment.NewLine);
        

        tbPrim.Text = sbOutput.ToString();
    
    else 
    
        tbPrim.Text = "Operation canceled by user or Exception";
    

#endregion

【讨论】:

嘿,我只使用 DataAdapter 来处理查询。 DataAdapter 执行,数据将流入 DataTable,这是本地的事情。从 DataTable 我想把数据放到 UI ListView 中。我不知道为什么这么难我只想将数据从 DoWork 处理到 UI 线程。我在这个项目上工作了一天多,我找不到解决方案。你的很好,但仍然不适合我 @LarryPetshow 将 DataTable 分配给 DoWork 事件的 REsult 属性。然后从 RunworkerCompleted 事件的结果中检索它。从那里分配它。 BGW 不遗余力地在主线程上引发 ReportProgress 和 RunWorkerCompleted 事件,因此不需要从这两个事件中调用。 如果我这样做,输出(结果)将是 System.Data.DataRow。而我的 ListView 则充满了这个词。但最重要的是,我编辑并复制了 DoWork 代码。 @LarryPetshow 自从我使用 DataAdapter 已经很久了。所以除了需要翻译的基本部分之外,我不记得太多了。

以上是关于C# 调用 ListView 不起作用(后台线程)的主要内容,如果未能解决你的问题,请参考以下文章

C# WPF - Listview 绑定不起作用

完整功能中的ajax调用后刷新jquery mobile listview不起作用

ListView setOnItemClickListener 通过添加按钮不起作用

在 c# 中线程化函数不起作用

从后台线程中的委托方法更改 UILabel 的文本不起作用

ListView Grid Item Hold 不起作用