理解SynchronizationContext,如何在Winform里面跨线程访问UI控件
SynchronizationContext 类是一个基类,可提供不带同步的自由线程上下文。 此类实现的同步模型的目的是使公共语言运行库内部的异步/同步操作能够针对不同的异步模型采取正确的行为。此模型还简化了托管应用程序为在不同的同步环境下正常工作而必须遵循的一些要求。同步模型的提供程序可以扩展此类并为这些方法提供自己的实现。(来自MSDN)
简而言之就是允许一个线程和另外一个线程进行通讯,SynchronizationContext在通讯中充当传输者的角色。另外这里有个地方需要清楚的,不是每个线程都附加SynchronizationContext这个对象,只有UI线程是一直拥有的。
前戏
之前在winform里面使用多线程或者异步的时候为了避免“访问了不是本线程创建的控件”都是设置窗体的CheckForIllegalCrossThreadCalls为不检查线程冲突,或者检测控件的InvokeRequired?!是的,当然没问题。(解释下,InvokeRequired属性是每个Control对象都具有的属性,它会返回true和false,当是true的时候,表示它在另外一个线程上面,这是必须通过Invoke,BeginInvoke这些方法来调用更新UI对象的方法,当是false的时候,有两种情况,1:位于当前线程上面,可以通过直接去调用修改UI对象的方法,2:位于不同的线程上,不过控件或窗体的句柄不存在。对于句柄是否存在的判断,可以通过IsHandleCreated来获取,如果句柄不存在,是不能调用Invoke...这些方法的,这时候你必须等待句柄的创建)
曾经对异步委托的使用简直上瘾,一天不用就不舒服。结果最近发现SynchronizationContext类真是个好东西啊。可以在窗体里面进行一下封装可以避免使用InvokeRequired去检测UI控件状态的麻烦。这里分享一段测试过程。
曾经以为工作十几年,Winform没啥学头了。其实呵呵了,要想在某个小领域突破是很难的。要成为真正的专家是马虎不得的,真是要活到老学到老。
测试代码
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | public partial class Form1 : Form { SynchronizationContext sc = null ; public Form1() { InitializeComponent(); sc = SynchronizationContext.Current; this .Load += Form1_Load; } private void Form1_Load( object sender, EventArgs e) { Thread thread = new Thread(( object state) => { int id = Thread.CurrentThread.ManagedThreadId; for ( int i = 0; i < 1000; i++) { Thread.Sleep(10); sc.Post(UpdateUI, "line " + i.ToString()); } }); thread.Start(); } /// <summary> /// This method is executed on the main UI thread. /// </summary> private void UpdateUI( object state) { int id = Thread.CurrentThread.ManagedThreadId; SendLogMessage( "UpdateUI thread:" + id); string text = state as string ; SendLogMessage(text); } /// <summary> /// UI线程回调方法,访问控件刷新显示 /// </summary> /// <param name="message">消息内容</param> private void LogMessageBack( object message) { rbxMessageInfo.AppendText(DateTime.Now + "->" + message.ToString() + Environment.NewLine); } /// <summary> /// 交给UI线程去排队显示消息 /// </summary> /// <param name="message"></param> private void SendLogMessage( string message) { sc.Post(LogMessageBack, message); } } |
大神对SynchronizationContext的封装
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | protected internal static System.AsyncCallback SyncCallback(System.AsyncCallback callback) { System.Threading.SynchronizationContext sc = System.Threading.SynchronizationContext.Current; if (sc == null ) { return callback; } return delegate (System.IAsyncResult asyncResult) { sc.Post( delegate ( object result) { callback((System.IAsyncResult)result); }, asyncResult); }; } protected void DataAction(Action action) { try { action(); } catch (System.Exception ex) { OperateLog.OpExcpLog( string .Format( "[{0}][{1}]功能操作执行异常,异常原因:{2}" , action.Method.GetType().FullName, action.Method.Name, ex.Message)); System.Windows.Forms.MessageBox.Show(EESException.getException(ex).Message, "异常警告" , System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Hand); } } protected void BeginAction(MethodDelegate action, object obj, params object [] args) { EForm.AsyncRequest asyncRequest = new EForm.AsyncRequest(action, obj, args, System.Threading.SynchronizationContext.Current); this .inAsyncAction = null ; this .OnBeginAction(asyncRequest); System.Threading.ThreadPool.QueueUserWorkItem( new System.Threading.WaitCallback( this .OnWaitCallback), asyncRequest); } protected void BeginAction( object proxy, string command, params object [] args) { MethodDelegate action = FastInvoke.CreateMethodDelegate(proxy.GetType().GetMethod(command)); this .BeginAction(action, proxy, args); } protected void BeginActionWithToolTip(MethodDelegate action, object obj, string toolTip, params object [] args) { EForm.AsyncRequest asyncRequest = new EForm.AsyncRequest(action, obj, args, System.Threading.SynchronizationContext.Current, toolTip); this .inAsyncAction = null ; this .OnBeginAction(asyncRequest); System.Threading.ThreadPool.QueueUserWorkItem( new System.Threading.WaitCallback( this .OnWaitCallback), asyncRequest); } protected void BeginActionWithToolTip( object proxy, string command, string toolTip, params object [] args) { MethodDelegate action = FastInvoke.CreateMethodDelegate(proxy.GetType().GetMethod(command)); this .BeginAction(action, proxy, new object [] { toolTip, args }); } protected virtual void OnBeginAction(EForm.AsyncRequest context) { if ( this .waitingForm.Visible) { throw new EESException( "后台正在执行,请稍候……" ); } this .waitingForm.Message = context.ToolTip; this .waitingForm.Show(); } protected virtual void OnEndAction(EForm.AsyncResponse context) { this .waitingForm.Visible = false ; } private void OnWaitCallback( object state) { EForm.AsyncRequest asyncRequest = (EForm.AsyncRequest)state; object result = null ; System.Exception exception = null ; try { result = asyncRequest.Method(asyncRequest.Obj, asyncRequest.Args); } catch (System.Exception ex) { exception = ex; } EForm.AsyncResponse state2 = new EForm.AsyncResponse(asyncRequest.Method, result, asyncRequest.Args, exception); System.Threading.SynchronizationContext context = asyncRequest.Context; if (context != null ) { context.Post( new System.Threading.SendOrPostCallback( this .OnSendOrPostCallback), state2); } } private void OnSendOrPostCallback( object state) { EForm.AsyncResponse asyncResponse = (EForm.AsyncResponse)state; this .OnEndAction(asyncResponse); if (asyncResponse.Exception != null ) { System.Windows.Forms.MessageBox.Show(EESException.getException(asyncResponse.Exception).Message); return ; } this .OnActionComplete(asyncResponse); } protected virtual void OnActionComplete(EForm.AsyncResponse context) { } |
这是摘录的项目里面基类窗体关于SynchronizationContext的封装,子类只要使用各种BeginAction就行了,然后在OnActionComplete里面处理访问UI控件的操作
作者:数据酷软件
出处:https://www.cnblogs.com/datacool/p/datacool_SynchronizationContext.html
关于作者:20年编程从业经验,持续关注MES/ERP/POS/WMS/工业自动化
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明。
联系方式: qq:71008973;wx:6857740733
基于人脸识别的考勤系统 地址: https://gitee.com/afeng124/viewface_attendance_ext
自己开发安卓应用框架 地址: https://gitee.com/afeng124/android-app-frame
WPOS(warehouse+pos) 后台演示地址: http://47.239.106.75:8080/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?