C#中的異步和同步


同步

同步(英語:Synchronization [ˌsɪŋkrənaɪ'zeɪʃn]),指對在一個系統中所發生的事件(event)之間進行協調,在時間上出現一致性與統一化的現象。說白了就是多個任務一個一個執行,同一時刻只有一個任務在執行。主要應用是互斥資源的訪問。下面是實現同步的一個例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplicationSyncTest
{
    class InterProcessSync
    {
        static void Main(string[] args)
     {
        string MutexName = "InterProcessSyncName";
        Mutex SyncNamed;     //聲明一個已命名的互斥對象             
        try
        {
            SyncNamed = Mutex.OpenExisting(MutexName);       //如果此命名互斥對象已存在則請求打開             
        }
        catch (WaitHandleCannotBeOpenedException)
        {
            SyncNamed = new Mutex(false, MutexName);         //如果初次運行沒有已命名的互斥對象則創建一個             
        }
        Task MulTask = new Task(() =>                        //多任務並行計算中的匿名方法,用委托也可以                     
        {
          while(ture)                                         //為了效果明顯而設計                         
          {
                Console.WriteLine("當前進程等待獲取互斥訪問權......");
                SyncNamed.WaitOne();
                Console.WriteLine("獲取互斥訪問權,訪問資源完畢,按回車釋放互斥資料訪問權.");
                Console.ReadLine();
                SyncNamed.ReleaseMutex();
                Console.WriteLine("已釋放互斥訪問權。");
           }
         } );

        MulTask.Start();
        MulTask.Wait();
     }

   }
}

 

以上程序編譯后,請運行兩個實例即兩個進程。就可以明顯的看出進程間的同步的實現。

線程

線程是進程中某個單一順序的控制流。也被稱為輕量進程(lightweight processes).計算機科學術語,指運行中的程序的調度單位.
當一個異步過程調用發出后,調用者不能立刻得到結果。實際處理這個調用的部件在完成后,通過狀態、通知和回調來通知調用者。以CAsyncSocket類為例(注意,CSocket從CAsyncSocket派生,但是其功能已經由異步轉化為同步),當一個客戶端通過調用Connect函數發出一個連接請求后,調用者線程立刻可以朝下運行。當連接真正建立起來以后,socket底層會發送一個消息通知該對象。這里提到執行部件和調用者通過三種途徑返回結果:狀態、通知和回調。可以使用哪一種依賴於執行部件的實現,除非執行部件提供多種選擇,否則不受調用者控制。如果執行部件用狀態來通知,那么調用者就需要每隔一定時間檢查一次,效率就很低(有些初學多線程編程的人,總喜歡用一個循環去檢查某個變量的值,這其實是一種很嚴重的錯誤)。如果是使用通知的方式,效率則很高,因為執行部件幾乎不需要做額外的操作。至於回調函數,其實和通知沒太多區別。

並行

一般指並行計算,是說同一時刻有多條指令同時被執行,這些指令可能執行於同一CPU的多核上,或者多個CPU上,或者多個物理主機甚至多個網絡中.

異步

與同步相對應,異步指的是讓CPU暫時擱置當前請求的響應,處理下一個請求,當通過輪詢或其他方式得到回調通知后,開始運行。多線程將異步操作放入另一線程中運行,通過輪詢或回調方法得到完成通知,但是完成端口,由操作系統接管異步操作的調度,通過硬件中斷,在完成時觸發回調方法, 此方式不需要占用額外線程

異步C#異步與多線程的異同點

異步和多線程兩者都可以達到避免調用線程阻塞的目的,從而提高軟件的可響應性。
異步操作無須額外的線程負擔,並且使用回調的方式進行處理,在設計良好的情況下,處理函數可以不必使用共享變量(即使無法完全不用,最起碼可以減少 共享變量的數量),減少了死鎖的可能。當然異步操作也並非完美無暇。編寫異步操作的復雜程度較高,程序主要使用回調方式進行處理,與普通人的思維方式有些 出入,而且難以調試。當需要執行I/O操作時,使用異步操作比使用線程+同步 I/O操作更合適。
多線程中的處理程序依然是順序執行,符合普通人的思維習慣,所以編程簡單。但是多線程的缺點也同樣明顯,線程的使用(濫用)會給系統帶來上下文切換的額外負擔。並且線程間的共享變量可能造成死鎖的出現。多線程的適用范圍則是那種需要長時間CPU運算的場合,例如耗時較長的圖形處理和算法執行。
control的同步和異步
(1)Control的Invoke和BeginInvoke與Delegate的Invoke和BeginInvoke是不同的。
(2)Control的Invoke和BeginInvoke的參數為delegate,委托的方法是在Control的線程上執行的,也就是我們平時所說的UI線程。
control的invoke:
private delegate void InvokeDelegate();
private void InvokeMethod(){
//C代碼段
}
private void butInvoke_Click(object sender, EventArgs e) {
//A代碼段.......
this.Invoke(new InvokeDelegate(InvokeMethod));
//B代碼段......
}

執行順序為:
A------>C---------------->B
解釋:

(1)A在UI線程上執行完后,開始Invoke,Invoke是同步
(2)代碼段B並不執行,而是立即在UI線程上執行InvokeMethod方法,即代碼段C。
(3)InvokeMethod方法執行完后,代碼段B才在UI線程上繼續執行。

control的begininvoke

private delegate void BeginInvokeDelegate();
private void BeginInvokeMethod(){
//C代碼段
}
private void butBeginInvoke_Click(object sender, EventArgs e) {
//A代碼段.......
this.BeginInvoke(new BeginInvokeDelegate(BeginInvokeMethod));
//B代碼段....}

執行順序:
A----------->B--------------->C
解釋:

(1)A在UI線程上執行完后,開始BeginInvoke,BeginInvoke是異步
(2)InvokeMethod方法,即代碼段C不會執行,而是立即在UI線程上執行代碼段B。
(3)代碼段B執行完后(就是說butBeginInvoke_Click方法執行完后),InvokeMethod方法,即代碼段C才在UI線程上繼續執行。

 
Control的Invoke和BeginInvoke的委托方法是在主線程,即UI線程上執行的。也就是說如果你的委托方法用來取花費時間長的數據,然后更新界面,千萬不能調用UI線程上調用Control.Invoke和Control.BeginInvoke,因為這些是依然阻塞UI線程的,造成界面的假死。

那么,這個異步到底是什么意思呢?
異步是指相對於調用BeginInvoke的線程異步,而不是相對於UI線程異步,你在UI線程上調用BeginInvoke ,當然不行了。(無論如何都在ui進程上執行)
BeginInvoke的原理是將調用的方法Marshal成消息,然后調用Win32 API中的RegisterWindowMessage()向UI窗口發送消息。
我們用Thread來調用BeginInvoke和Invoke
private Thread invokeThread;
private delegate void invokeDelegate();
private void StartMethod(){
//C代碼段......
Control.Invoke(new invokeDelegate(invokeMethod));
//D代碼段......
}
private void invokeMethod(){
//E代碼段
}
private void butInvoke_Click(object sender, EventArgs e) {
//A代碼段.......
invokeThread = new Thread(new ThreadStart(StartMethod));
invokeThread.Start();
//B代碼段......
}
 

A------>(Start一開始B和StartMethod的C就同時執行)---->(C執行完了,不管B有沒有執行完,invokeThread把消息封送(invoke)給UI線程,然后自己等待)---->UI線程處理完butInvoke_Click消息后,處理invokeThread封送過來的消息,執行invokeMethod方法,即代碼段E,處理往后UI線程切換到invokeThread線程。

解釋:
1。UI執行A
2。UI開線程InvokeThread,B和C同時執行,B執行在線程UI上,C執行在線程invokeThread上。
3。invokeThread封送消息給UI,然后自己等待,UI處理完消息后,處理invokeThread封送的消息,即代碼段E
4。UI執行完E后,轉到線程invokeThread上,invokeThread線程執行代碼段D

private Thread beginInvokeThread;
private delegate void beginInvokeDelegate();
private void StartMethod(){
//C代碼段......
Control.BeginInvoke(new beginInvokeDelegate(beginInvokeMethod));
//D代碼段......
}
private void beginInvokeMethod(){
//E代碼段
}
private void butBeginInvoke_Click(object sender, EventArgs e) {
//A代碼段.......
beginInvokeThread = new Thread(new ThreadStart(StartMethod));
beginInvokeThread .Start();
//B代碼段......
}

 A在UI線程上執行----->beginInvokeThread線程開始執行,UI繼續執行代碼段B,並發地invokeThread執行代碼段C-------------->不管UI有沒有執行完代碼段B,這時beginInvokeThread線程把消息封送給UI,單自己並不等待,繼續向下執行-------->UI處理完butBeginInvoke_Click消息后,處理beginInvokeThread線程封送過來的消息。

解釋:
1。UI執行A
2。UI開線程beginInvokeThread,B和C同時執行,B執行在線程UI上,C執行在線程beginInvokeThread上。
3。beginInvokeThread封送消息給UI,然后自己繼續執行代碼D,UI處理完消息后,處理invokeThread封送的消息,即代碼段E
有點疑問:如果UI先執行完畢,是不是有可能過了段時間beginInvokeThread才把消息封送給UI,然后UI才繼續執行封送的消息E。如圖淺綠的部分。

Control的BeginInvoke是相對於調用它的線程,即beginInvokeThread相對是異步的。
因此,我們可以想到。如果要異步取耗費長時間的數據,比如從數據庫中讀大量數據,我們應該這么做。
(1)如果你想阻止調用線程,那么調用代碼(三),代碼段D刪掉,C改為耗費長時間的操作,因為這個操作是在另外一個線程中做的。代碼段E改為更新界面的方法。
(2)如果你不想阻止調用線程,那么調用代碼(四),代碼段D刪掉,C改為耗費長時間的操作,因為這個操作是在另外一個線程中做的。代碼段E改為更新界面的方法。
 
委托還能寫成下面的兩種形式
        public delegate void NoparamDelegate(Tools toolPanel);
        public void hideTool()
        {
            NoparamDelegate hide = delegate (Tools toolPanel)
            {
               toolPanel.Visibility = Visibility.Hidden;
               btnTool.Switch = false;
            };
            hide.Invoke(toolPanel);
            }

        public void hideTool()
        {
              Dispatcher.Invoke(()=> {
              toolPanel.Visibility = Visibility.Hidden;
              btnTool.Switch = false;
            });
            }

 

在實際操作用,如果用Thread操作界面相關的代碼,會出現錯誤:    調用線程必須為 STA,因為許多 UI 組件都需要

那么我們該如何處理呢?

1、委托實現

public delegate void NoParamDelegate();

//要實現的方法

public void Func()
 {

          //使用ui元素    

 }

線程函數中做如此調用:  System.Windows.Application.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,new NoParamDelegate(Func)); 

 

2、指定線程為STA

Thread NetServer = new Thread(new ThreadStart(NetServerThreadFunc));
 NetServer .SetApartmentState(ApartmentState.STA);
 NetServer .IsBackground = true;

 NetServer.Start();

 線程函數中做如此調用:

 System.Windows.Threading.Dispatcher.Run();

 


免責聲明!

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



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