简单的窗体程序,包含以下三个类
PagedList.cs
//数据列表类
public class PagedList
{
public int totalLines { get; set; }
public int totalPages { get; set; }
public int pageNo { get; set; }
public DataTable table { get; set; }
public PagedList()
{
}
public PagedList(int TotalLines, int TotalPages, int PageNo, DataTable Table)
{
this.totalLines = TotalLines;
this.totalPages = TotalPages;
this.pageNo = PageNo;
this.table = Table;
}
}
GUIDAO.cs
//模拟数据访问接口
public class GuiDao
{
public static PagedList ListSectioned(string businessClazz,PagedList pl)
{
if (pl == null)
{
pl = new PagedList();
pl.totalLines = 10000;
pl.table = new DataTable();
pl.table.Columns.AddRange(new DataColumn[] { new DataColumn("rowNo"), new DataColumn("clazz"), new DataColumn("someValue") });
}
else
{
Random r = new Random();
int fl = pl.table.Rows.Count;
int tl = pl.totalLines;
int temp = r.Next(100);
temp = temp < tl - fl ? temp : tl - fl;
for (int i = 0; i < temp; i++)
{
DataRow dr = pl.table.NewRow();
dr["clazz"] = "SomeClazz";
dr["rowNo"] = fl + i + 1;
dr["someValue"] = r.Next(1000);
pl.table.Rows.Add(dr);
}
}
return pl;
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private delegate void InvokeHandler(PagedList pl);
private delegate void MsgHandler(string msg);
//回调方法
private void ListSectionedCallback(bool isSuccess, PagedList pl, string msg)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new InvokeHandler(p => { this.dataGridView1.DataSource = pl.table; }), pl);
}
}
//状态栏更新
private void DispMsg(bool isSuccess, int finishedLines, string msg)
{
msg = (isSuccess ? "Finished Lines: " + finishedLines : msg);
if (this.InvokeRequired)
{
this.BeginInvoke(new MsgHandler(m => this.toolStripStatusLabel1.Text = m), msg);
}
}
//分段读取数据
private void ListSectioned(string businessClazz, PagedList pl, Action<bool, PagedList, string> listSectionedCallback, Action<bool, int, string> dispMsg)
{
try
{
pl = GuiDao.ListSectioned(businessClazz, pl);
listSectionedCallback(true, pl, "调用GuiDao失败");
DispMsg(true, pl.table.Rows.Count, "读取失败");
int fl = pl.table.Rows.Count;
int tl = pl.totalLines;
while (fl < tl)
{
pl = GuiDao.ListSectioned(businessClazz, pl);
Thread.Sleep(5);
listSectionedCallback(true, pl, "调用GuiDao失败");
DispMsg(true, pl.table.Rows.Count, "读取失败");
}
}
catch (Exception ex)
{
DispMsg(true, pl.table.Rows.Count, "读取失败,原因" + ex.Message);
}
}
private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(() => ListSectioned("someClazz", null, this.ListSectionedCallback, this.DispMsg));
thread.IsBackground = true;
thread.Start();
}
private void Form1_Load(object sender, EventArgs e)
{
this.toolStripStatusLabel1.Text = "";
this.dataGridView1.AutoGenerateColumns = true;
}
}
窗体布局如图
预想的运行效果是点击按钮后前台异步地加载数据 状态栏更新显示
然而运行时会出现三种情况:
1.正常运行
2.表格只加载一次数据,状态栏会更新
3. this.BeginInvoke(new InvokeHandler(p => { this.dataGridView1.DataSource = pl.table; }), pl);这一句报“集合已修改 可能无法执行枚举操作”的异常
求高人解惑
------解决思路----------------------
pl.table只有一个实例,它绑定到dataGridView上后,工作线程还在向pl.table添加行。而dataGridView同时还要从里面读数据显示。这样多线程访问非线程安全的对象,就造成了你看到了不稳定的现象。
应该是读数据操作的对象和UI绑定的对象分离,读出一部分数据后,BeginInvoke到UI线程把它们添加进绑定的数据。
------解决思路----------------------
this.BeginInvoke(new InvokeHandler(p => { this.dataGridView1.DataSource = pl.table; }), pl);
改为:
this.Invoke(new InvokeHandler(p => { this.dataGridView1.DataSource = pl.table; }), pl);
或者使用WPF,使用通知形式让界面自动更改。
------解决思路----------------------
第一,滥用阻塞式线程逻辑(循环里边启动一堆线程,你的程序的复杂性和bug程指数放大了),你得程序中不应该有while 循环语句。
第二,定是获取数据,不要搞什么“5毫秒刷新”这种逻辑。有数据、得到事件/消息通知之后才去读数据,而不是轮询。