如何在multithreading应用程序中安全地填充数据和Refresh()DataGridView?

我的应用程序有一个DataGridView对象和一个MousePos类型的List。 MousePos是一个自定义类,它包含鼠标X,Y坐标(“Point”类型)和此位置的运行计数。 我有一个线程(System.Timers.Timer)每秒引发一次事件,检查鼠标位置,添加和/或更新此列表上鼠标位置的计数。

我想有一个类似的运行线程(再次,我认为System.Timers.Timer是一个不错的选择),它会再次引发一次事件,每秒自动刷新()DataGridView,以便用户可以看到数据屏幕更新。 (就像TaskManager一样。)

不幸的是,调用DataGridView.Refresh()方法会导致VS2005停止执行,并注意到我遇到了跨线程的情况。

如果我理解正确,我现在有3个主题:

  • 主UI线程
  • MousePos List线程(Timer)
  • DataGridView刷新线程(计时器)

为了看看我是否可以在主线程上刷新()DataGridView,我在表单中添加了一个名为DataGridView.Refresh()的按钮,但这(奇怪的是)没有做任何事情。 我发现了一个似乎表明如果我设置DataGridView.DataSource = null并返回到我的List的主题,它会刷新datagrid。 确实这有效,但只能通过按钮(在主线程上处理)。


所以这个问题变成了两个问题:

  1. 将DataGridView.DataSource设置为null并返回到我的List是一种可接受的刷新数据网格的方法吗? (对我来说似乎效率低下……)
  2. 如何在multithreading环境中安全地执行此操作?

这是我到目前为止编写的代码(C#/。Net 2.0)

public partial class Form1 : Form { private static List mousePositionList = new List(); private static System.Timers.Timer mouseCheck = new System.Timers.Timer(1000); private static System.Timers.Timer refreshWindow = new System.Timers.Timer(1000); public Form1() { InitializeComponent(); mousePositionList.Add(new MousePos()); // ANSWER! Must have at least 1 entry before binding to DataSource dataGridView1.DataSource = mousePositionList; mouseCheck.Elapsed += new System.Timers.ElapsedEventHandler(mouseCheck_Elapsed); mouseCheck.Start(); refreshWindow.Elapsed += new System.Timers.ElapsedEventHandler(refreshWindow_Elapsed); refreshWindow.Start(); } public void mouseCheck_Elapsed(object source, EventArgs e) { Point mPnt = Control.MousePosition; MousePos mPos = mousePositionList.Find(ByPoint(mPnt)); if (mPos == null) { mousePositionList.Add(new MousePos(mPnt)); } else { mPos.Count++; } } public void refreshWindow_Elapsed(object source, EventArgs e) { //dataGridView1.DataSource = null; // Old way //dataGridView1.DataSource = mousePositionList; // Old way dataGridView1.Invalidate(); // <= ANSWER!! } private static Predicate ByPoint(Point pnt) { return delegate(MousePos mPos) { return (mPos.Pnt == pnt); }; } } public class MousePos { private Point position = new Point(); private int count = 1; public Point Pnt { get { return position; } } public int X { get { return position.X; } set { position.X = value; } } public int Y { get { return position.Y; } set { position.Y = value; } } public int Count { get { return count; } set { count = value; } } public MousePos() { } public MousePos(Point mouse) { position = mouse; } } 

您必须像所有其他控件一样更新主UI线程上的网格。 请参阅control.Invoke或Control.BeginInvoke。

UPDATE! – 我部分地找到了“Pro .NET 2.0 Windows窗体和C#中的客户控件”一书中第1部分的答案

我原本以为Refresh()没有做任何事情,我需要调用Invalidate()方法,告诉Windows重新控制我的控件。 (通常是马上,但如果您需要保证现在重新绘制它,那么请立即调用Update()方法。)

  dataGridView1.Invalidate(); 

但是,事实certificate, Refresh()方法仅仅是一个别名:

  dataGridView1.Invalidate(true); dataGridView1.Update(); // <== forces immediate redraw 

我发现的唯一故障是,如果dataGridView中没有数据,那么无效的数据将刷新控件。 我不得不重新分配数据源。 然后它在那之后运作良好。 但仅限于行数(或列表中的项目) - 如果添加了新项目,dataGridView将不会意识到有更多行要显示。

因此,似乎在将数据源(List或Table)绑定到Datasource时,dataGridView会对项(行)进行计数,然后在内部进行设置,并且永远不会检查是否删除了新的行/项或行/项。 这就是之前重复绑定数据源的原因。

现在想弄清楚如何更新dataGridView中显示的行数,而不必重新绑定数据源......有趣,有趣,有趣! 🙂


在做了一些挖掘之后,我想我的答案是我的问题的第2部分 (又名安全的multithreading):

而不是使用System.Timers.Timer ,我发现我应该使用System.Windows.Forms.Timer

发生这样的事件,使得回调中使用的方法自动发生在主线程上。 没有跨线程问题!

声明如下:

 private static System.Windows.Forms.Timer refreshWindow2; refreshWindow2 = new Timer(); refreshWindow2.Interval = 1000; refreshWindow2.Tick += new EventHandler(refreshWindow2_Tick); refreshWindow2.Start(); 

方法是这样的:

 private void refreshWindow2_Tick(object sender, EventArgs e) { dataGridView1.Invalidate(); } 

看起来你的答案就在那里! 只是在cawse你很好奇如何跨线程调用回到ui:所有控件都有一个Invoke()方法(或BEginInvoke() – 如果你想异步做事),这用于调用任何方法主UI线程上下文中的控件。 因此,如果您要从另一个线程调用datagridview,则需要执行以下操作:

 public void refreshWindow_Elapsed(object source, EventArgs e) { // we use anonymous delgate here as it saves us declaring a named delegate in our class // however, as c# type inference sometimes need a bit of 'help' we need to cast it // to an instance of MethodInvoker dataGridView1.Invoke((MethodInvoker)delegate() { dataGridView1.Invalidate(); }); }