如何添加到ListBox线程安全(通过扩展方法)

时间:2016-11-18 13:24:51

标签: c# multithreading listbox

我想将一个项目添加到表单上的列表框中,将其向下滚动到最后一个条目然后刷新它。我想在并行的ForEach循环中执行此操作 为此,我在网上找到了一种扩展方法,并根据我的需要进行了更改。现在我收到错误消息:“跨线程操作无效:控制'listBox1'从其创建的线程以外的线程访问。”。我理解错误是工作线程试图访问ListBox。实际上我可以看到主线程在收到错误之前可以更新ListBox。调试器也告诉我错误是在线“int visibleItems ...”
如何才能做到这一点?

public static class MyClass
{
    public static void AddItemThreadSafe(this System.Windows.Forms.ListBox lb, object item)
    {
        int visibleItems = lb.ClientSize.Height / lb.ItemHeight;
        if (lb.InvokeRequired)
        {
            lb.Invoke(new MethodInvoker(delegate
            {
                lb.Items.Add(item);
                lb.TopIndex = Math.Max(lb.Items.Count - visibleItems + 1, 0);
                lb.Refresh();
            }));
        }
        else
        {
            lb.Items.Add(item);
            lb.TopIndex = Math.Max(lb.Items.Count - visibleItems + 1, 0);
            lb.Refresh();
        }
    }
}

1 个答案:

答案 0 :(得分:-1)

您正在通过UI线程以外的线程访问ClientSize属性,这会导致行int visibleItems = lb.ClientSize.Height / lb.ItemHeight; 上的异常

如果你完全预先判断了visibleItems,你可以摆脱异常,但它仍然不是线程安全的代码:

public static void AddItemThreadSafe(this System.Windows.Forms.ListBox lb, object item)
{
    if (lb.InvokeRequired)
    {
        lb.Invoke(new MethodInvoker(delegate
        {
            lb.Items.Add(item);
            lb.TopIndex = Math.Max(lb.Items.Count - lb.ClientSize.Height / lb.ItemHeight + 1, 0);
            lb.Refresh();
        }));
    }
    else
    {
        lb.Items.Add(item);
        lb.TopIndex = Math.Max(lb.Items.Count - lb.ClientSize.Height / lb.ItemHeight + 1, 0);
        lb.Refresh();
    }
}

你需要的是添加项目和刷新,一个原子操作(如果调度程序决定让它去另一个调用,则不允许执行暂停)。 您可以使用锁定:

private static readonly Object obj = new Object();

public static void AddItemThreadSafe(this System.Windows.Forms.ListBox lb, object item)
{
    if (lb.InvokeRequired)
    {
        lb.Invoke(new MethodInvoker(delegate
        {
            lock (obj)
            {
                // thread unsafe code
                lb.Items.Add(item);
                lb.TopIndex = Math.Max(lb.Items.Count - lb.ClientSize.Height / lb.ItemHeight + 1, 0);
            }        
        }));
    }
    else
    {
        lock (obj)
        {
            // thread unsafe code
            lb.Items.Add(item);
            lb.TopIndex = Math.Max(lb.Items.Count - lb.ClientSize.Height / lb.ItemHeight + 1, 0);
        }
    }
}

但是,如果你运行代码,使用Parallel.For,例如

Parallel.For(0, 1000, (x) =>
{
   listBox1.AddItemThreadSafe(x);
});

表格会冻结。您需要确保UI可以呈现到目前为止的所有内容,因此您可以将其更改为:

Parallel.For(0, 1000, (x) =>
{
    listBox1.AddItemThreadSafe(x);
    Application.DoEvents();
});

它会正确呈现表单,就像这里:

The form rendering the items in parallel