2016-11-18 151 views
-1

我想添加一个项目到窗体上的列表框,向下滚动到最后一个条目,然后刷新它。我想在并行ForEach循环中完成此操作。
为此,我在网上找到了一个扩展方法,并将其改为我的需要。现在,我收到错误消息:“跨线程操作无效:从其创建的线程以外的线程访问控件'listBox1'”。我明白错误是工作线程试图访问列表框。事实上,我可以看到主线程可以在接收错误之前更新列表框。此外调试器告诉我,错误是在行“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

尝试声明变量“visibleItems”在代表块内部和其他时钟内。是的,代码重复,但应该工作 – Pyfhon

+0

尝试[MethodImpl(MethodImplOptions.Synchronized)]上面的函数AddItemThreadSafe' –

+0

我试过[MethodImpl(MethodImplOptions.Synchronized)]但它不能解决问题。我也在代理块中移动了“visibleItems”,但是这种改变导致程序冻结。 – Manngo

回答

-1

您通过比UI线程,这将导致对int visibleItems = lb.ClientSize.Height/lb.ItemHeight;

如果prescind visibleItems完全线路异常其他线程访问ClientSize属性,你可以摆脱例外,但它仍然不会是线程安全的代码:

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

形式将冻结。你需要确保用户界面可以使一切都那么远,所以你可以把它改成:正确

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

它将呈现形式,就像这里:

The form rendering the items in parallel

+0

这只是*糟糕的*建议。为了让所有人花费他们所有的时间编排回UI线程,将会浪费大量的时间,而不会产生任何收益。你也不应该像这样使用'DoEvents';这不是该方法的适当用法,只会导致任何遵循这些建议的人受到伤害。 – Servy