進程與進程間通信


多線程開發掃盲系列第一編:進程與進程間通信

 

1. 操作系統的進程與線程管理   

2. 進程的啟動和終止   

3. 進程通信   

 3.1 通過剪貼版進程交換信息   

 3.2 FileSystemWatch實現進程同步   

 3.3 使用內存映射文件實現進程通信   

 3.4 進程間的通知機制   

 

1.進程與線程管理

進程(process)是一個具有獨立功能的程序在一個數據集合上的一次動態執行過程。這個定義太理論化了,用一句通俗的話取代它:進程可以簡單理解為一個正在運行的程序。
程序與進程的區別可以用圖形像地表達出來。


    Window設計了兩種代碼運行環境,用戶模式(User Mode)和核心模式(kernel Mode),普通的應用程序運行於用戶模式中,而操作系統的關鍵代碼(比如負責分配與回收內存、創建和銷毀進程等功能的代碼)運行於核心模式下。 在windows中,”系統調用”主要指win32API中的特定函數,所以,windows應用程序通過調用win32API函數來實現從”用戶模式”到”核心模式”的轉換
   

    句柄與系統核心對像
    位於操作系統內核中,僅允許運行於”核心模式”下的代碼訪問的數據被稱為”核心對像”,操作系統在運行時,會在系統核心不斷地創建和銷毀”核心對像”,為了便於跟蹤和訪問這些對像,操作系統為這些對像分配了標識,這是一個32位的整數,被稱為”句柄”。許多win32 API函數通過句柄來定位所要訪問的系統核心對像。在.NET托管環境中,.NET應用程序對”普通對像”和”核心對像”不加區分,使用New關鍵字就可以創建任何一種類型的對像,而對像的銷毀工作郵CLR負責。
   

    Windows操作系統使用線程作為CPU調度的基本單位,一個進程可以划分多個線程,也可以只有一個線程。它擁有一個線程標識(ThreadID),一組CPU寄存器,兩個堆棧和一個專有的線程局部存儲區(Thread Local Storage,TLS)。屬於同一個進程的線程共享進程所擁有的資源。
進程是系統分配各種資源(比如內存)的單位,而線程則是操作系統分配CPU(即處理機調度)的基本單位。

 

2.進程的啟動與終止

    .NET應用程序控制進程的核心是Process類,Process類繼承自Component類,通常又稱為Process組件。Process組件代表一個托管進程,底層封裝的是操作系統的本地進程。另一個重要的類是ProcessStartInfo類,這個類封裝了進程啟動時的各種控制參數。

如下繼承結構圖


使用Process.Start方法啟動進程
Process.Start(“IExplore.exe”)
Process.Start(“IExplore.exe”,”www.baidu.com”)
有時候我們希望向進程傳送一些控制信息,比如此進程打開一個網頁時最小化,可以這么來做
ProcessStartInfo info = new ProcessStartInfo("IExplore.exe");
info.WindowStyle=ProcessWindowStyle.Minimized;  //自動最小化
info.Arguments="www.sina.cn";  //自動訪問新浪網
Process.Start(info);  //啟動進程   


通過調用CloseMainWindow方法發出的結束進程運行的請求不會強制應用程序立即退出,它相當於用戶直接點擊主窗口上的關閉按鈕。應用程序可以在退出前請求用戶確認,也可以拒絕退出。

Kill方法強制關閉一個進程,與CloseMainWindow方法不同,Kill方法實際上是請求操作系統直接結束進程,它不給要關閉的進程保存數據的機會,因此除非要保存的進程沒有任何數據需保存,否則不要采用Kill方法直接結束某個進程。

 

3.進程通信

3.1 通過剪貼版進程交換信息

所謂進程通信,是指正在運行的進程之間相互交換信息。
每個進程都擁有自己的地址空間,其他進程不能直接訪問,因此通常需要通過一個第三方媒介間接地在進程之間交換信息。
剪貼板是最常用的進程間交換信息的媒介之一。

剪貼版相當於一個"物品臨時寄存器",一次只能保存一個"物品",而且這個"物品"是大家共享的,比如使用work復制了一段文本在剪貼板上,現在又使用"畫圖"程序將一幅圖放在剪貼板上,則圖片數據將替換掉文本數據。再比如使用畫圖程序將一幅畫放在剪貼板上,則work,寫字板,photoshop等其它應用程序都可以從剪貼板中獲取這些數據。

剪貼板中可以保存多種類型數據,.NET定義了一個DataFormats類,定義了剪貼板中可以存放的數據類型,如下圖

字段名稱

說明

Bitmap

Windows位圖格式

Dib

Windows與設備無關的位圖(DIB)格式

EnhancedMetafile

Windows增強型圖元文件格式

Html

由Html數據組成的文本

MetafilePict

Windows圖元文件格式,Windows客體不直接使用此格式

OemText

標准Windows原始設備制造商(OEM)文本格式

Palette

Windows調色板格式

Rtf

由Rich Text Format(RTF)數據組成的文本

Serializable

可序列化的對像

StringFormat

Windows窗體字符串類格式,Windows窗體使用此格式存儲字符串對像

Text

標准ANSI文本格式

UnicodeText

標准Windows Unicode文本格式

 如下示例,復制幾個文件或圖片,點擊剪貼板上有什么按鈕,看結果

實現代碼:

        private void btnShowBoard_Click(object sender, EventArgs e)
        {
            IDataObject data = Clipboard.GetDataObject();  //獲取剪貼板上的數據
            richTextBox1.Text = "";
            if (data == null)
                return;
            string[] str = data.GetFormats();   //獲取剪貼板上數據類型
            foreach (string s in str)
            {
                richTextBox1.AppendText(s+"\n");
            }
        }

 

 

如下示例,即基於剪貼板交換數據,打開兩個此程序,程序A點裝入圖片-復制到剪貼板,程序B點從剪貼板粘貼。即可看到數據在兩個進程間交換

核心代碼:

    //裝入圖片
    private void btnLoadImage_Click(object sender, EventArgs e)
        {
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                bmp = new Bitmap(openFileDialog1.FileName);
            }
        }

     //保存到剪貼板       
      private void btnCopyToBoard_Click(object sender, EventArgs e)
        {
            MyPic mypic = new MyPic { image = bmp, info = info };
            IDataObject dataobj = new DataObject(mypic);
            dataobj.SetData(DataFormats.UnicodeText, info);
            dataobj.SetData(DataFormats.Bitmap, bmp);
            Clipboard.SetDataObject(dataobj, true);   
        }

    //從剪貼板粘貼
        private void btnPasteFromBoard_Click(object sender, EventArgs e)
        {
            if (Clipboard.ContainsData("UseClipboard.MyPic") == false)
                return;

            IDataObject clipobj = Clipboard.GetDataObject();
            //將數據轉換為需要的類型
            MyPic mypicobj = clipobj.GetData("UseClipboard.MyPic") as MyPic;
            //從數據對象中分解出需要的數據
            info = mypicobj.info;
            pictureBox1.Image = mypicobj.image;
        }

剪貼板用起來非常方便,但它有個缺點,它沒法通知其他進程數據已放到剪貼板上了,除非在等待接收數據的進程中設計一個輔助線程定時監控剪貼板,在數據來時主動從剪貼板中獲取數據,但這並不是最佳方式。

3.2 FileSystemWatch實現進程同步

FileSystemWatcher是.Net Framework所提供的一個組件,它可以監控特定的文件夾或文件,比如在此文件夾中某文件被刪除或內容被改變時引發對應的事件。如下所示兩個程序一個用於讀,一個用於寫,當在frmwrite修改了文件點保存時,frmreader會同步顯示文件更新后的內容

文件寫入的方法
 using (StreamWriter sw = new StreamWriter(new FileStream(FileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read), Encoding.Default))
   {
       sw.Write(richTextBox1.Text);
   }

文件監控的代碼

namespace FileReader
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        string FileName;

       //載入文件
        public void LoadFile()
        {
            try
            {
                using (StreamReader sr = new StreamReader(new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Encoding.Default))
                {
                    richTextBox1.Text = sr.ReadToEnd();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        public void SetupFileSystemWatch()
        {
            fileSystemWatcher1.Filter = Path.GetFileName(FileName);   //監控的文件
            fileSystemWatcher1.Path = Path.GetDirectoryName(FileName);  //監控的文件路徑
            fileSystemWatcher1.NotifyFilter = NotifyFilters.Size;   //當文件大小改變時,觸發事件
        }

        private void btnCheck_Click(object sender, EventArgs e)
        {
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                FileName = openFileDialog1.FileName;
                LoadFile();
                SetupFileSystemWatch();
            }
        }

        private void fileSystemWatcher1_Changed(object sender, FileSystemEventArgs e)
        {
            LoadFile();
        }

    }
}

FileSystemWatcher組件的常用事件

Changed

當更改指定文件夾中的文件和目錄時發生

Created

當在指定文件夾中創建文件和目錄時發生

Deleted

刪除指定文件夾或目錄時發生

Renamed

重命名指定文件夾中 或目錄時發生

3.3 使用內存映射文件實現進程通信

所謂內存映射就是在內存中開辟出一塊存放數據的專用區域,這區域往往與硬盤上特定的文件相對應。進程將這塊內存區域映射到自己的地址空間中,訪問它就像是訪問普通的內存一樣,.NET中使用MemoryappedFile對像表示一個內存映射文件,通過它的CreateFromFile方法根據磁盤現有文件創建內存映射文件,MemoryMappedFile對像創建之后,不能直接對其讀寫,還須通過一個MemoryMappedViewAccessor對像來訪問。

如下示例:輸入兩個數,保存到內存取映射文件,然后再打開一個程序點擊提取即可把內存映射中的數據提取出來

代碼如下:

    private int FileSize = 1024 * 1024;  //設為映射文件大小
        private MemoryMappedFile file = null;  
        private MemoryMappedViewAccessor accor = null;
        private void Init()
        {
            file = MemoryMappedFile.CreateOrOpen("UseMMFBetweenProcess", FileSize);  //創建內存取映射文件
            accor = file.CreateViewAccessor();  //創建映射文件視圖
            toolStripStatusLabel1.Text = "內存文件映射或創建成功";
        }
        private MyStructure data;
        private void btnSave_Click(object sender, EventArgs e)
        {
            data.IntValue = Convert.ToInt32(txtInt.Text);
            data.FloatValue =float.Parse(txtFloat.Text);
            accor.Write<MyStructure>(0,ref data);   //將結構對像保存到映射文件中
            toolStripStatusLabel1.Text = "數據已保存到內文件中";

        }

        private void btnGet_Click(object sender, EventArgs e)
        {
            accor.Read<MyStructure>(0, out data);  //從映射文件中取出結構對像
            txtInt.Text = data.IntValue.ToString(); ;
            txtFloat.Text = data.FloatValue.ToString();
            toolStripStatusLabel1.Text = "成功從內存中提取了數據";
        }

3.4 進程間的通知機制

 進程之間的數據傳送有多種方式,但大多數進程通信手段都缺乏一種通知機制,本節介紹一種比較簡便的.NET線程同步對像Mutext和EventWaitHandle實現進程通知機制的方法

示例如下:點擊發送端程序中的的click me,接收端窗體會記錄點擊次數

 

 //發送端代碼

 public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private EventWaitHandle handle;
        private const string ProcessSynchronizeEventName = "ProcessSynchronizeEvent";
        private void button1_Click(object sender, EventArgs e)
        {
            handle.Set();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            try
            {
                handle = EventWaitHandle.OpenExisting(ProcessSynchronizeEventName);
                if (handle != null)
                {
                    MessageBox.Show("只能運行一個實例");
                    handle = null;
                    Close();
                }
            }
            catch (WaitHandleCannotBeOpenedException)
            {
                handle = new EventWaitHandle(false, EventResetMode.ManualReset, ProcessSynchronizeEventName);
                labInfo.Text = "eventhandle已創建";
            }

        }
    }

 

//接收端代碼

 public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private EventWaitHandle hEvent = null;
        private const string MyProcess = "ProcessSynchronizeEventResponsor";
        private const string ProcessSynchronizeEventName = "ProcessSynchronizeEvent";

        private void GetEventHandle()
        {
            try
            {
                hEvent = EventWaitHandle.OpenExisting(ProcessSynchronizeEventName);
                if (hEvent != null)
                {
                    Thread th = new Thread(WaitForHandle);
                    th.IsBackground = true;
                    th.Start();
                  
                }
            }
            catch (WaitHandleCannotBeOpenedException)
            {
                MessageBox.Show("請先運行程序ProcessSynchronizeEventSource的一個實例");
                Close();
            }
        }
        private int count;
        void WaitForHandle()
        {
            while (true)
            {
                hEvent.WaitOne();
                count++;
                string info="服務端進程點擊了" + count+"次";
                Action<string> showinfo = delegate(string a)
                {
                    labInfo.Text = a;
                };
                this.Invoke(showinfo, info);
                hEvent.Reset();
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            try
            {
                Mutex m = Mutex.OpenExisting(MyProcess);
                if (m != null)
                {
                    MessageBox.Show("已有一個實例在運行");
                    Close();
                }
            }
            catch (WaitHandleCannotBeOpenedException)
            {
                Mutex m = new Mutex(false, MyProcess);
            }
            GetEventHandle();
        }
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM