在Invoke或者BeginInvoke的使用中无一例外地使用了委托Delegate,至于委托的本质请参考个人另外一随笔:对.net事件的见解。html
关于这个问题的最主要的缘由已是dotnet程序员众所周知的,我在此费点笔墨再次记录到本身的日志,以便往后提醒一下本身。程序员
Windows GUI程序是基于消息机制的,有个主线程维护着一个消息泵。这个消息泵让windows程序生生不息。编程
Windows GUI程序的消息循环
windows
Windows程序有个消息队列,窗体上的全部消息是这个队列里面消息的最主要来源。这里的while循环使用了GetMessage()这个方法,这是个阻塞方法,也就是队列为空时方法就会被阻塞,从而这个while循环中止运动,这避免了一个程序把cpu平白无故地耗尽,让其它程序难以获得响应。固然在某些须要cpu最大限度运动的程序里面就可使用另外的方法,例如某些3d游戏或者及时战略游戏中,通常会使用PeekMessage()这个方法,它不会被windows阻塞,从而保证整个游戏的流畅和比较高的帧速。api
这个主线程维护着整个窗体以及上面的子控件。当它获得一个消息,就会调用DispatchMessage方法派遣消息,这会引发对窗体上的窗口过程的调用。窗口过程里面固然是程序员提供的窗体数据更新代码和其它代码。数据结构
public static void Main(string[] args) { Form f = new Form(); Application.Run(f); }
Dotnet窗体程序封装了上述的while循环,这个循环就是经过Application.Run方法启动的。app
若是从另一个线程操做windows窗体上的控件,就会和主线程产生竞争,形成不可预料的结果,甚至死锁。所以windows GUI编程有一个规则,就是只能经过建立控件的线程来操做控件的数据,不然就可能产生不可预料的结果。异步
所以,dotnet里面,为了方便地解决这些问题,Control类实现了ISynchronizeInvoke接口,提供了Invoke和BeginInvoke方法来提供让其它线程更新GUI界面控件的机制。ide
public interface ISynchronizeInvoke { [HostProtection(SecurityAction.LinkDemand, Synchronization=true, ExternalThreading=true)] IAsyncResult BeginInvoke(Delegate method, object[] args); object EndInvoke(IAsyncResult result); object Invoke(Delegate method, object[] args); bool InvokeRequired { get; } }
若是是从建立控件的线程外操做windows窗体控件,那么就须要使用Invoke或者BeginInvoke方法,经过一个委托把调用封送到控件所属的线程上执行。函数
Windows消息机制是windows平台上的线程或者进程间通讯机制之一。Windows消息值其实就是定义的一个数据结构,最重要的是消息的类型,它就是一个整数;而后就是消息的参数。消息的参数能够表示不少东西。
Windows提供了一些api用来向一个线程的消息队列发送消息。所以,一个线程能够向另外一个线程的消息队列发送消息从而告诉对方作什么,这样就完成了线程间的通讯。有些api发送消息须要一个窗口句柄,这种函数能够把消息发送到指定窗口的主线程消息队列;而有些则能够直接经过线程句柄,把消息发送到该线程消息队列中。
用消息机制通讯
SendMessage是windows api,用来把一个消息发送到一个窗口的消息队列。这个方法是个阻塞方法,也就是操做系统会确保消息的确发送到目的消息队列,而且该消息被处理完毕之后,该函数才返回。返回以前,调用者将会被暂时阻塞。
PostMessage也是一个用来发送消息到窗口消息队列的api函数,但这个方法是非阻塞的。也就是它会立刻返回,而无论消息是否真的发送到目的地,也就是调用者不会被阻塞。
Invoke or BeginInvoke
Invoke或者BeginInvoke方法都须要一个委托对象做为参数。委托相似于回调函数的地址,所以调用者经过这两个方法就能够把须要调用的函数地址封送给界面线程。这些方法里面若是包含了更改控件状态的代码,那么因为最终执行这个方法的是界面线程,从而避免了竞争条件,避免了不可预料的问题。若是其它线程直接操做界面线程所属的控件,那么将会产生竞争条件,形成不可预料的结果。
使用Invoke完成一个委托方法的封送,就相似于使用SendMessage方法来给界面线程发送消息,是一个同步方法。也就是说在Invoke封送的方法被执行完毕前,Invoke方法不会返回,从而调用者线程将被阻塞。
使用BeginInvoke方法封送一个委托方法,相似于使用PostMessage进行通讯,这是一个异步方法。也就是该方法封送完毕后立刻返回,不会等待委托方法的执行结束,调用者线程将不会被阻塞。可是调用者也可使用EndInvoke方法或者其它相似WaitHandle机制等待异步操做的完成。
可是在内部实现上,Invoke和BeginInvoke都是用了PostMessage方法,从而避免了SendMessage带来的问题。而Invoke方法的同步阻塞是靠WaitHandle机制来完成的。
若是你的后台线程在更新一个UI控件的状态后不须要等待,而是要继续往下处理,那么你就应该使用BeginInvoke来进行异步处理。
若是你的后台线程须要操做UI控件,而且须要等到该操做执行完毕才能继续执行,那么你就应该使用Invoke。不然,在后台线程和主截面线程共享某些状态数据的状况下,若是不一样步调用,而是各自继续执行的话,可能会形成执行序列上的问题,虽然不发生死锁,可是会出现不可预料的显示结果或者数据处理错误。
能够看到ISynchronizeInvoke有一个属性,InvokeRequired。这个属性就是用来在编程的时候肯定,一个对象访问UI控件的时候是否须要使用Invoke或者BeginInvoke来进行封送。若是不须要那么就能够直接更新。在调用者对象和UI对象同属一个线程的时候这个属性返回false。在后面的代码分析中咱们能够看到,Control类对这一属性的实现就是在判断调用者和控件是否属于同一个线程的。
经过一个委托来进行同步方法的异步调用(有点绕嘴),也是.net提供的异步调用机制之一。可是Delegate.BeginInvoke方法是从ThreadPool取出一个线程来执行这个方法,以得到异步执行效果的。也就是说,若是采用这种方式提交多个异步委托,那么这些调用的顺序没法获得保证。并且因为是使用线程池里面的线程来完成任务,使用频繁,会对系统的性能形成影响。
Delegate.BeginInvoke一样也是将一个委托方法封送到其它线程,从而经过异步机制执行一个方法。调用者线程则能够在完成封送之后去继续它的工做。可是这个方法封送到的最终执行线程是运行库从ThreadPool里面选取的一个线程。
这里须要纠正一个误区,那就是Control类上的异步调用BeginInvoke并无开辟新的线程完成委托任务,而是让界面控件的所属线程完成委托任务的。看来异步操做就是开辟新线程的说法不必定准确。
public IAsyncResult BeginInvoke(Delegate method, params object[] args) { using (new MultithreadSafeCallScope()) { return (IAsyncResult) this.FindMarshalingControl().MarshaledInvoke(this, method, args, false); } } public object Invoke(Delegate method, params object[] args) { using (new MultithreadSafeCallScope()) { return this.FindMarshalingControl().MarshaledInvoke(this, method, args, true); } }
这里的FindMarshalingControl方法经过一个循环向上回溯,从当前控件开始回溯父控件,直到找到最顶级的父控件,用它做为封送对象。例如,咱们调用窗体上一个进度条的Invoke方法封送委托,可是实际上会回溯到主窗体,经过这个控件对象来封送委托。由于主窗体是主线程消息队列相关的,发送给主窗体的消息才能发送到界面主线程消息队列。
咱们能够看到Invoke和BeginInvoke方法使用了一样的实现,只是MarshaledInvoke方法的最后一个参数值不同。
private object MarshaledInvoke(Control caller, Delegate method, object[] args, bool synchronous) { int num; if (!this.IsHandleCreated) { throw new InvalidOperationException(SR.GetString("ErrorNoMarshalingThread")); } if (((ActiveXImpl) this.Properties.GetObject(PropActiveXImpl)) != null) { IntSecurity.UnmanagedCode.Demand(); } bool flag = false; if ((SafeNativeMethods.GetWindowThreadProcessId(new HandleRef(this, this.Handle), out num) == SafeNativeMethods.GetCurrentThreadId()) && synchronous) { flag = true; } ExecutionContext executionContext = null; if (!flag) { executionContext = ExecutionContext.Capture(); } ThreadMethodEntry entry = new ThreadMethodEntry(caller, method, args, synchronous, executionContext); lock (this) { if (this.threadCallbackList == null) { this.threadCallbackList = new Queue(); } } lock (this.threadCallbackList) { if (threadCallbackMessage == 0) { threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage"); } this.threadCallbackList.Enqueue(entry); } if (flag) { this.InvokeMarshaledCallbacks(); } else { //终于找到你了,PostMessage UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero); } if (!synchronous) //若是是异步,那么立刻返回吧 { return entry; } if (!entry.IsCompleted) //同步调用没结束,阻塞起来等待吧 { this.WaitForWaitHandle(entry.AsyncWaitHandle); } if (entry.exception != null) { throw entry.exception; } return entry.retVal; }
怎么样,咱们终于看到PostMessage了吧?经过windows消息机制实现了封送。而须要封送的委托方法做为消息的参数进行了传递。关于其它的代码这里不做进一步解释。
public bool InvokeRequired { get { using (new MultithreadSafeCallScope()) { HandleRef ref2; int num; if (this.IsHandleCreated) { ref2 = new HandleRef(this, this.Handle); } else { Control wrapper = this.FindMarshalingControl(); if (!wrapper.IsHandleCreated) { return false; } ref2 = new HandleRef(wrapper, wrapper.Handle); } int windowThreadProcessId = SafeNativeMethods.GetWindowThreadProcessId(ref2, out num); int currentThreadId = SafeNativeMethods.GetCurrentThreadId(); return (windowThreadProcessId != currentThreadId); } } }
终于看到了,这是在判断windows窗体线程和当前的调用者线程是不是同一个,若是是同一个就没有必要封送了,直接访问这个GUI控件吧。不然,就不要那么直接表白了,就须要Invoke或者BeginInvoke作媒了。
出处:https://www.cnblogs.com/worldreason/archive/2008/06/09/1216127.html
=============================================================================
再发一个SendMessage的使用实例代码,也是从网上找来的。
进程间通信,其实用SendMessage就能够实现,刚找了一下之前作的东西,正好是进程间通信的,主要是主程序调用校验程序,校验程序返回校验结果。
using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.Diagnostics; namespace SendMsg { #region 结构体定义 [StructLayout(LayoutKind.Sequential)] public struct COPYDATASTRUCT { public int dwData; public int cbData; public int lpData; } //定义要传递的Struct [StructLayout(LayoutKind.Sequential)] struct SendMsgInfo { /// <summary> /// 参数(单号) /// </summary> [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)] public string AccessionNum; /// <summary> /// 参数(原文) /// </summary> [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5000)] public string ReportTxt; /// <summary> /// 参数(Ris用户) /// </summary> [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)] public string RisUid; /// <summary> /// 调用方法名 /// </summary> [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)] public string FunctionNum; /// <summary> /// 端口 /// </summary> public int Handle; /// <summary> /// 返回值 /// </summary> public bool Result; /// <summary> /// Err信息 /// </summary> [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 200)] public string ResultMsg; } #endregion class CaSendMsg { Process pro; const int WM_COPYDATA = 0x004A; private string _winFrmName = @"校验程序"; private string _path = @"校验.exe"; private int _interval = 500; private int _maxTimes = 5; /// <summary> /// 窗体名称 /// </summary> public string WinFrmName { set { _winFrmName = value; } get { return _winFrmName; } } /// <summary> /// 程序路径 /// </summary> public string Path { set { _path = value; } get { return _path; } } /// <summary> /// 等待轮序频率 秒/次 /// </summary> public int Interval { set { _interval = value; } get { return _interval; } } /// <summary> /// 上限次数 /// </summary> public int MaxTimes { set { _maxTimes = value; } get { return _maxTimes; } } [DllImport("user32", EntryPoint = "SendMessageA")] public static extern int SendMessage(int hWnd, int wMsg, int wParam, ref COPYDATASTRUCT lParam); [DllImport("User32.dll", EntryPoint = "FindWindow")] public static extern int FindWindow(string lpClassName, string lpWindowName); /// <summary> /// ca校验 /// </summary> /// <param name="h"></param> /// <returns></returns> public bool CACheck(SendMsgInfo h) { int hWnd = GetWinFrmHwnd(); if (hWnd <= 0) return false; return CACheck(h, hWnd); } /// <summary> /// 获取程序句柄 /// </summary> /// <returns></returns> private int GetWinFrmHwnd() { int hWnd = FindWindow(null, _winFrmName); if (hWnd <= 0) { hWnd = StartNewPro(_path); } return hWnd; } /// <summary> /// 开启新的校验进程 /// </summary> /// <param name="path"></param> /// <returns></returns> private int StartNewPro(string path) { try { pro = new Process(); pro.StartInfo.FileName = path; pro.Start(); return pro.MainWindowHandle.ToInt32(); } catch { return 0; } } public bool CACheck(SendMsgInfo info, int hWnd) { try { int size = Marshal.SizeOf(typeof(SendMsgInfo)); byte[] Bytes = new byte[size]; //根据定义的尺寸分配内存块 GCHandle GC = GCHandle.Alloc(Bytes, GCHandleType.Pinned); IntPtr ptr1 = GC.AddrOfPinnedObject(); //得到Struct对应的IntPtr Marshal.StructureToPtr(info, ptr1, false); COPYDATASTRUCT SendData = new COPYDATASTRUCT(); SendData.lpData = ptr1.ToInt32(); SendData.cbData = size; if (hWnd > 0) { SendMessage(hWnd, WM_COPYDATA, 0, ref (SendData)); } return true; } catch { return false; } } public bool getCaResult(System.Windows.Forms.Message m) { COPYDATASTRUCT RecvData = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT)); SendMsgInfo info = (SendMsgInfo)Marshal.PtrToStructure((IntPtr)RecvData.lpData, typeof(SendMsgInfo)); //h.ResultMsg; return info.Result; } } }
接收消息
//接收消息 protected override void DefWndProc(ref System.Windows.Forms.Message m) { switch (m.Msg) { case SendMsg.WM_COPYDATA: COPYDATASTRUCT RecvData = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT)); SendMsgInfo h = (SendMsgInfo)Marshal.PtrToStructure((IntPtr)RecvData.lpData, typeof(SendMsgInfo)); textBox1.Text = h.MessageType.ToString(); textBox2.Text = h.MessageText; //返回校验结果 SendMsgInfo returnInfo = new SendMsgInfo(); returnInfo.Handle = h.Handle; returnInfo.MessageNum = h.MessageNum; returnInfo.Result = true; returnInfo.ResultMsg = ""; SendMsg c = new SendMsg(); c.Send(returnInfo, returnInfo.Handle); break; default: base.DefWndProc(ref m); break; } }
出处:https://bbs.csdn.net/topics/390650236 请查看 #20 给出的代码
http://www.javashuo.com/article/p-keoepqcx-ne.html
=====================================================================================
再给一个使用的示例,不一样之处是参考传递的消息的结构,
namespace CshapMessage { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { IntPtr hwnd; const int WM_COPYDATA = 0x004A; public struct COPYDATASTRUCT { public IntPtr dwData; public int cData; [MarshalAs(UnmanagedType.LPStr)] public string lpData; } [DllImport("User32.dll")] public static extern int SendMessage(int hwnd, int msg, int wParam, ref COPYDATASTRUCT IParam); [DllImport("User32.dll")] public static extern int FindWindow(string lpClassName, string lpWindowName); public MainWindow() { InitializeComponent(); this.Title = "CshapMessage"; this.Loaded += MainWindow_Loaded; this.Closed += MainWindow_Closed; } private void MainWindow_Closed(object sender, EventArgs e) { try { HwndSource.FromHwnd(hwnd).RemoveHook(WndProc); } catch (Exception) { } } private void MainWindow_Loaded(object sender, RoutedEventArgs e) { hwnd = new WindowInteropHelper(this).Handle; HwndSource.FromHwnd(hwnd).AddHook(new HwndSourceHook(WndProc)); } /// <summary> /// 向C++程序 CshapMessage发送消息 /// </summary> /// <param name="nMessgeId"></param> /// <param name="strSend"></param> /// <returns></returns> private bool CshapSendMessage(int nMessgeId, String strSend) { int WINDOW_HANDLE = FindWindow(null, "VcMessage");//VcMessage为向C++程序发送的窗口名称 if (WINDOW_HANDLE != 0) { COPYDATASTRUCT cdata; cdata.dwData = (IntPtr)100;//这里能够传入一些自定义的数据,但只能是4字节整数 cdata.lpData = strSend;//消息字符串 cdata.cData = System.Text.Encoding.Default.GetBytes(strSend).Length+1;//注意,这里的长度是按字节来算的 SendMessage(WINDOW_HANDLE, WM_COPYDATA, 0, ref cdata); } else { return false; } return true; } private void button_Click(object sender, RoutedEventArgs e) { String strSend = "C#发送的信息"; int nMessageId = 100; if (CshapSendMessage(nMessageId,strSend)) { MessageBox.Show("发送消息成功"); } else { MessageBox.Show("消息发送失败,请打开VcMessage程序"); } } //接收消息。 private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (msg == WM_COPYDATA) { COPYDATASTRUCT cdata = new COPYDATASTRUCT(); Type mytype = cdata.GetType(); cdata = (COPYDATASTRUCT)Marshal.PtrToStructure(lParam, mytype); switch (cdata.dwData.ToInt32()) { case 1: { string strRecv = cdata.lpData; break; } default: break; } } return IntPtr.Zero; } } }