多線程開發掃盲系列第一編:進程與進程間通訊windows
1. 操做系統的進程與線程管理 多線程
2. 進程的啓動和終止 app
3. 進程通訊 函數
3.1 經過剪貼版進程交換信息 this
3.3 使用內存映射文件實現進程通訊 操作系統
3.4 進程間的通知機制 線程
進程(process)是一個具備獨立功能的程序在一個數據集合上的一次動態執行過程。這個定義太理論化了,用一句通俗的話取代它:進程能夠簡單理解爲一個正在運行的程序。
程序與進程的區別能夠用圖形像地表達出來。設計
Window設計了兩種代碼運行環境,用戶模式(User Mode)和核心模式(kernel Mode),普通的應用程序運行於用戶模式中,而操做系統的關鍵代碼(好比負責分配與回收內存、建立和銷燬進程等功能的代碼)運行於核心模式下。 在windows中,」系統調用」主要指win32API中的特定函數,因此,windows應用程序經過調用win32API函數來實現從」用戶模式」到」核心模式」的轉換
code
句柄與系統核心對像
位於操做系統內核中,僅容許運行於」核心模式」下的代碼訪問的數據被稱爲」核心對像」,操做系統在運行時,會在系統核心不斷地建立和銷燬」核心對像」,爲了便於跟蹤和訪問這些對像,操做系統爲這些對像分配了標識,這是一個32位的整數,被稱爲」句柄」。許多win32 API函數經過句柄來定位所要訪問的系統核心對像。在.NET託管環境中,.NET應用程序對」普通對像」和」核心對像」不加區分,使用New關鍵字就能夠建立任何一種類型的對像,而對像的銷燬工做郵CLR負責。
Windows操做系統使用線程做爲CPU調度的基本單位,一個進程能夠劃分多個線程,也能夠只有一個線程。它擁有一個線程標識(ThreadID),一組CPU寄存器,兩個堆棧和一個專有的線程局部存儲區(Thread Local Storage,TLS)。屬於同一個進程的線程共享進程所擁有的資源。
進程是系統分配各類資源(好比內存)的單位,而線程則是操做系統分配CPU(即處理機調度)的基本單位。
.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方法直接結束某個進程。
所謂進程通訊,是指正在運行的進程之間相互交換信息。
每一個進程都擁有本身的地址空間,其餘進程不能直接訪問,所以一般須要經過一個第三方媒介間接地在進程之間交換信息。
剪貼板是最經常使用的進程間交換信息的媒介之一。
剪貼版至關於一個"物品臨時寄存器",一次只能保存一個"物品",並且這個"物品"是你們共享的,好比使用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;
}
剪貼板用起來很是方便,但它有個缺點,它無法通知其餘進程數據已放到剪貼板上了,除非在等待接收數據的進程中設計一個輔助線程定時監控剪貼板,在數據來時主動從剪貼板中獲取數據,但這並非最佳方式。
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 |
重命名指定文件夾中 或目錄時發生 |
所謂內存映射就是在內存中開闢出一塊存放數據的專用區域,這區域每每與硬盤上特定的文件相對應。進程將這塊內存區域映射到本身的地址空間中,訪問它就像是訪問普通的內存同樣,.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 = "成功從內存中提取了數據";
}
進程之間的數據傳送有多種方式,但大多數進程通訊手段都缺少一種通知機制,本節介紹一種比較簡便的.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(); } }