当前位置: 代码迷 >> C# >> 一个异步小程序的有关问题,详见代码
  详细解决方案

一个异步小程序的有关问题,详见代码

热度:60   发布时间:2016-05-05 04:48:43.0
一个异步小程序的问题,详见代码
简单的窗体程序,包含以下三个类
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,使用通知形式让界面自动更改。
------解决思路----------------------
引用:
        //分段读取数据
        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);
            }
        }


第一,滥用阻塞式线程逻辑(循环里边启动一堆线程,你的程序的复杂性和bug程指数放大了),你得程序中不应该有while 循环语句。

第二,定是获取数据,不要搞什么“5毫秒刷新”这种逻辑。有数据、得到事件/消息通知之后才去读数据,而不是轮询。

  相关解决方案