C#中的WinForm的消息機制簡述,及消息機制下Invoke,和BeginInvoke的使用和區別

在Invoke或者BeginInvoke的使用中無一例外地使用了委託Delegate,至於委託的本質請參考個人另外一隨筆:對.net事件的見解。html

1、爲何Control類提供了Invoke和BeginInvoke機制?

關於這個問題的最主要的緣由已是dotnet程序員衆所周知的,我在此費點筆墨再次記錄到本身的日誌,以便往後提醒一下本身。程序員

一、windows程序消息機制

Windows GUI程序是基於消息機制的,有個主線程維護着一個消息泵。這個消息泵讓windows程序生生不息。編程

Windows GUI程序的消息循環
windows

 

Windows程序有個消息隊列,窗體上的全部消息是這個隊列裏面消息的最主要來源。這裏的while循環使用了GetMessage()這個方法,這是個阻塞方法,也就是隊列爲空時方法就會被阻塞,從而這個while循環中止運動,這避免了一個程序把cpu平白無故地耗盡,讓其它程序難以獲得響應。固然在某些須要cpu最大限度運動的程序裏面就可使用另外的方法,例如某些3d遊戲或者及時戰略遊戲中,通常會使用PeekMessage()這個方法,它不會被windows阻塞,從而保證整個遊戲的流暢和比較高的幀速。api

這個主線程維護着整個窗體以及上面的子控件。當它獲得一個消息,就會調用DispatchMessage方法派遣消息,這會引發對窗體上的窗口過程的調用。窗口過程裏面固然是程序員提供的窗體數據更新代碼和其它代碼。數據結構

二、dotnet裏面的消息循環

public static void Main(string[] args)
{
   Form f = new Form();
   Application.Run(f);
}    

Dotnet窗體程序封裝了上述的while循環,這個循環就是經過Application.Run方法啓動的。app

 

三、線程外操做GUI控件的問題

若是從另一個線程操做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方法,經過一個委託把調用封送到控件所屬的線程上執行。函數

2、消息機制---線程間和進程間通訊機制

一、window消息發送

Windows消息機制是windows平臺上的線程或者進程間通訊機制之一。Windows消息值其實就是定義的一個數據結構,最重要的是消息的類型,它就是一個整數;而後就是消息的參數。消息的參數能夠表示不少東西。

Windows提供了一些api用來向一個線程的消息隊列發送消息。所以,一個線程能夠向另外一個線程的消息隊列發送消息從而告訴對方作什麼,這樣就完成了線程間的通訊。有些api發送消息須要一個窗口句柄,這種函數能夠把消息發送到指定窗口的主線程消息隊列;而有些則能夠直接經過線程句柄,把消息發送到該線程消息隊列中。

用消息機制通訊

 

SendMessage是windows api,用來把一個消息發送到一個窗口的消息隊列。這個方法是個阻塞方法,也就是操做系統會確保消息的確發送到目的消息隊列,而且該消息被處理完畢之後,該函數才返回。返回以前,調用者將會被暫時阻塞。

PostMessage也是一個用來發送消息到窗口消息隊列的api函數,但這個方法是非阻塞的。也就是它會立刻返回,而無論消息是否真的發送到目的地,也就是調用者不會被阻塞。

二、Invoke and BeginInvoke

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類對這一屬性的實現就是在判斷調用者和控件是否屬於同一個線程的。

3、Delegate.BeginInvoke

經過一個委託來進行同步方法的異步調用(有點繞嘴),也是.net提供的異步調用機制之一。可是Delegate.BeginInvoke方法是從ThreadPool取出一個線程來執行這個方法,以得到異步執行效果的。也就是說,若是採用這種方式提交多個異步委託,那麼這些調用的順序沒法獲得保證。並且因爲是使用線程池裏面的線程來完成任務,使用頻繁,會對系統的性能形成影響。

Delegate.BeginInvoke一樣也是將一個委託方法封送到其它線程,從而經過異步機制執行一個方法。調用者線程則能夠在完成封送之後去繼續它的工做。可是這個方法封送到的最終執行線程是運行庫從ThreadPool裏面選取的一個線程。

這裏須要糾正一個誤區,那就是Control類上的異步調用BeginInvoke並無開闢新的線程完成委託任務,而是讓界面控件的所屬線程完成委託任務的。看來異步操做就是開闢新線程的說法不必定準確。 

4、用Reflector察看一些相關代碼

一、Control.BeginInvoke and Control.Invoke

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方法的最後一個參數值不同。

二、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消息機制實現了封送。而須要封送的委託方法做爲消息的參數進行了傳遞。關於其它的代碼這裏不做進一步解釋。

三、InvokeRequired

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;
        }
    }
}

 

 

出處:https://blog.csdn.net/Sayesan/article/details/88314972

https://blog.csdn.net/yl2isoft/article/details/20222679

相關文章
相關標籤/搜索