背景
在我做WinForm开发的过程中,经常会遇到耗时操作或阻塞操作。他们会引发软件的卡顿甚至假死,严重影响软件的使用。因此,这类耗时或阻塞的操作一般都会使用异步的方式去执行,不影响主线程(UI线程)与用户间的交互。但多个线程竞争读写同一个资源往往会造成意想不到的意外结果,UI界面也是一种资源,所以跨线程修改UI界面往往被加以限制。而在Winform中,跨线程修改UI界面同样是不被允许的。在子线程中修改界面控件时Visual Studio会报出如下错误:
![](https://i-blog.csdnimg.cn/blog_migrate/d0b271352dce4cce12c90c5c731723d5.png)
解决思路
.Net提供了很多跨线程修改UI的方法,每种方法也有与之对应的工具类。我最常用的方法是ThreadPool+ delegate的方式完成跨线程对UI对的修改。
实例代码
1、定义委托,把内容写在控件里面
代码思路 将要处理的数据放到ConcurrentQueue 中,然后开启多个线程去处理数据,处理完成后,再到队列中获取下一个待处理数据。
ConcurrentQueue 表示线程安全的先进先出 (FIFO) 集合,属于 System.Collections.Concurrent 命名空间下的一个数据结构
直接上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | /// <summary> /// 多线程处理数据(无返回值) /// </summary> /// <typeparam name="T">数据类型</typeparam> /// <param name="list">待处理数据</param> /// <param name="action">数据处理方法(有参数无返回值)</param> /// <param name="count">处理线程数量</param> /// <param name="waitFlag">是否等待执行结束</param> static void RunTask<T>(List<T> list, Action<T> action, int threadCount = 5, bool waitFlag = true ) { ConcurrentQueue<T> queue = new ConcurrentQueue<T>(list); Task[] tasks = new Task[threadCount]; for ( int i = 0; i < threadCount; i++) { tasks[i] = Task.Run(() => { while (queue.TryDequeue( out T t)) { action(t); } }); } if (waitFlag) { Task.WaitAll(tasks); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /// <summary> /// 多线程处理数据(返回处理后列表) /// </summary> /// <typeparam name="T">数据类型</typeparam> /// <param name="list">待处理数据</param> /// <param name="func">数据处理方法(有参数有返回值)</param> /// <param name="threadCount">处理线程数量</param> /// <returns>数据处理后结果</returns> static List<T> RunTask<T>(List<T> list, Func<T, T> func, int threadCount = 5) { var result = new List<T>(); ConcurrentQueue<T> queue = new ConcurrentQueue<T>(list); Task<List<T>>[] tasks = new Task<List<T>>[threadCount]; for ( int i = 0; i < threadCount; i++) { tasks[i] = Task.Run<List<T>>(() => { var rList = new List<T>(); while (queue.TryDequeue( out T t)) { rList.Add(func(t)); } return rList; }); } Task.WaitAll(tasks); for ( int i = 0; i < threadCount; i++) { result.AddRange(tasks[i].Result); } return result; } |
调用方法
1 2 3 4 5 6 7 | List< int > list = new List< int >() { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; //输出列表中的数据,且加上“action”字符前缀 RunTask< int >(list, d => { Console.WriteLine( "action" + d); }); //对列表中数据都执行 “*2” 的操作 var result = RunTask< int >(list, d => { return d * 2; }); result.ForEach(d => Console.WriteLine(d)); |
最后的话 上面的代码只是简单的实现了对数据的处理,并没有考虑到对内存的使用限制,一般的项目中使用还是可以的。 最后我尝试了下,生成一个100M的列表,然后将其加载到ConcurrentQueue,监控程序内存占用,发现没有很大的内存占用变化。 | |
2、定义方法,线程调用
1 2 3 4 5 6 7 8 9 10 11 | private void button3_Click( object sender, EventArgs e) { ThreadPool.QueueUserWorkItem( new WaitCallback(dao), "test" ); } private void dao( object url) { IDbConnection conn = new SqlConnection(txtbigcrmui.Text); SetText( "\r\n" + "start" ); ......................... SetText( "\r\n" + "end" ); } |
3、更新ui控件
1 | SetText( "\r\n" + "start" ); |
总结
1、使用delegate实现跨线程更新UI;
2、使用ThreadPool实现多线程执行方法,避免卡顿;
以上就是C#-WinForm跨线程修改UI界面的示例的详细内容,更多关于C#-WinForm跨线程的资料请关注脚本之家其它相关文章!