在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
当你运行这个程序时,当耗时操作结束后,啪嚓一下,程序出异常了: Control.Invoke&Control.BeginInvokeControl.Invoke和Control.BeginInvoke就是“发短信”的方法,如果使用Control.Invoke发短信,那么甲线程就会像个痴情的汉子,一直等待着乙线程的回音,而如果使用Control.BeginInvoke发送短信,那发完短信后,甲线程就会忙活自己的,等乙线程处理完再来瞧瞧。 注意:有人看到了BeginInvoke方法来了个Begin,心里可能在想,这是异步的特征啊,那是不是像上篇文章中使用delegate的BeginInvoke方法那样,启动一个worker thread?记住,这里的BeginInvoke是异步操作,但不是通过线程来实现的,具体方式后面有介绍。 我们先来看看如何使用Control.Invoke和Control.BeginInvoke(本文为了区分Control.BeginInvoke与delegate.BeginInvoke的区别,一直带上Control前缀)来发短信: 1: /// <summary> OK,运行,异常消失的无影无踪。我们在QuertyDataBase方法里的第8行,SetText方法里,也就是上面的第12行设置断点,再运行,然后打开VS的Threads窗口(打开方法:Debug菜单->Windows->Threads),会发现QueryDataBase运行在一个Worker Thread里,不同于Main Thread,而SetText却在Main Thread线程里运行:
上图是命中QueryDataBase中的断点的Threads窗口
上图是命中SetText方法中的断点时Threads窗口,看看黄色箭头所指的地方,哈哈,SetText运行在与UI同一个线程里,没有另起灶炉,这是怎么办到的呢?请看下一节。
发短信的方法知道了,想不想知道发短信的原理?如果不想知道您可以离开了。 PostMessage没有别的什么招儿,还是上Reflector。 我们发现,不管是Invoke还是BeginInvoke,都是调用 this.FindMarshalingControl().MarshaledInvoke(this, method, args, true/false); 这样的方法,只是第三个参数有不同,通过第三个参数的名字synchronous也猜测的出来是什么意思了。再来阅读MarshaledInvoke方法的代码“去掉干扰部分,取其精华”,差不多就明白了大致流程。 构建一个ThreadMethodEntry ThreadMethodEntry entry = new ThreadMethodEntry(caller, method, args, synchronous, executionContext); 然后将其添加到Control的theadCallbackList队列中。 然后使用Win32 API RegisterWindowMessage注册一个message(关于message可参见第一篇文章)。 threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage"); threadCallbackMessage是一个整型值,这个在后面会有用处。 MSDN对RegisterWindowMessage方法的介绍: RegisterWindowMessage Function The RegisterWindowMessage function defines a new window message that is guaranteed to be unique throughout the system. The message value can be used when sending or posting messages. 然后使用PostMessage将该消息发送到主窗体,这就是我们说的“发短信”。 这里的PostMessage和前面介绍的SendMessage是孪生兄弟,但亦有一些不同。 引用MSDN的介绍: SendMessage Function PostMessage Function 稍微解释下:SendMessage向窗体发送一个消息,然后一直等待,直到指定的窗体处理完该消息后才返回,而PostMessage消息不同,其将消息发送到创建窗体的线程的消息队列上,然后理解返回而不去等待该消息是否处理完。 不管是Invoke还是BeginInvoke,都是使用PostMessage。不同的是Invoke会使用this.WaitForWaitHandle(entry.AsyncWaitHandle);等待消息处理结束。而BeginInvoke立即返回。 上面只是介绍了发送消息,那消息是怎么被处理的呢? WndProc还记得前面介绍的WndProc方法么,Control里的WndProc方法就是“短信处理中心”了。所有发送到窗体的短信默认(因为WndProc是虚方法,可被覆盖)都会在这里处理。要了解更多WndProc方法的细节,参见前面的文章。 在WndProc里我们发现了这样的代码: 1: if ((m.Msg == threadCallbackMessage) && (m.Msg != 0)) 这里的threadCallbackMessage不就是刚才注册消息时的返回值么。 哦,现在思路基本上理清楚了,先注册个消息,然后发送给窗体线程,让窗体线程自己处理设置控件属性的方法,这样就不会出现线程安全的问题了。 |
请发表评论