《CLR via C#》讀書筆記-異步編程(四)

APM是一種編程方式,而不是C#編程語言的一種新特性(雖然實現APM而提供BeginXXX、EndXXX方法,但這個並沒有改變XXX方法的本質,因此APM只是一種編程方式)
APM異常
正式因爲APM只是一種編程方式,因此其異常處理方式與正常的方式一致。但有一點需要說明,一般情況下,會將try語句塊放置於調用EndXXX的方法內部。
APM與計算限制
在《CLR via C#》中多線程和異步以及後面同步鎖的這些部分中都包含了“計算限制”這個名稱,說一下個人理解。計算限制操作是指利用計算機的性能去完成非I/O類型操作的操作統稱。
APM編程模式一般是用於I/O相關的操作中,但是其仍然可以適用於“計算限制操作”中。其原理如下:定義好委託之後,編譯器會將委託編譯,並生成相關的方法,其中就有一個BeginInvoke和EndInvoke方法,而有了這兩個方法就可以在編程時採用APM編程方式。以.NET自帶的Func

public delegate TResult Func<in T, out TResult>(T arg)

定義完委託後,編譯器會將委託編譯爲如下:

public sealed class Func<T,TResult>:MulticastDelegate{
    public Func(Object object,IntPtr method);
    public TResult Invoke(T arg);
    public IAsyncResult BeginInvoke(T arg,AsyncCallBack callback,Object object);
    public TResult EndInvoke(IAsyncResult result);
}

就是通過第三個、第四個方法完成了計算限制操作的APM編程。其中callback回調方法的委託的具體定義爲:

[SerializableAttribute]
[ComVisibleAttribute(true)]
public delegate void AsyncCallback(
    IAsyncResult ar
)

計算限制操作使用APM編程的例子如下:
假設需完成某求和的方法,方法如下:

private static UInt64 Sum(UInt64 n){
    UInt64 sum=0;
    for(UInt64 i=0;i<n;i++){
        checked{
            sum+=i;
        }
    }
    return sum;
}

這個方法正好符合Func

public static void Main(){
    Func<UInt64,UInt64> sumDelegate=Sum;

    sumDelegate.BeginInvoke(1000000,SumIsDone,sumDelegate); //最後一個參數很有意思

    Console.ReadLine();
}

private static void SumIsDone(IAsyncResult result){
    var sumDelegate = (Func<UInt64,UInt64>)result.AsyncState;
    try{
        UInt64 theSumResult=sumDelegate.EndInvoke(result);
        Console.WriteLine("最終求和結果爲:"+theSumResult);
    }catch(Exception ex){
        throw ex;
    }
}

APM注意事項
1、默認情況下,CLR在異步操作完成後通過線程池調用預先定義的回調方法。若不使用系統默認,而是通過手動查詢異步操作是否完成,進而再去調用回調方法,這也是可行的。我在《CLR via C#》讀書筆記-異步編程(三) 中說明了如何使用IAsyncResult對象屬性來判斷異步是否完成。但是不建議這樣做。這樣會導致線程阻塞,線程池創建新的線程。
2、APM編程中一定要調用EndXXX方法
3、在調用EndXXX方法時,一定要使用與調用BeginXXX相同的對象。在上面的例子的有一行註釋“最後一個參數很有意思”,這個參數的含義就是將其封裝在IAsyncResult的AsyncState屬性中,這樣就可以在調用EndXXX的方法中得到,在調用EndXXX時能夠保證,其與調用BeginXXX的對象是相同的。
4、APM編程方式無取消機制(從Vista開始,Win32中新增了一個CancelSynchronousIO函數,可以允許一個線程取消另外一個線程執行同步的操作)
5、APM會導致內存消耗。因爲在異步操作創建時就會創建IAsyncResult對象。不過這種對內存的佔用還是小
6、有些操作必須是同步的,因此在這些過程中是不能使用APM編程方式的。例如通過FileStream創建文件只能是一個同步操作。特別明顯的感受就是在網絡映射磁盤或者是U盤中創建文件,在創建文件的時候就會出現卡死的的現象。
7、FileStream特有的問題。FileStream很有意思,其在初始化的時候需要通過File.ASynchronous標誌位進行標示該對象上所有的操作時同步還是異步。若使用默認時(即,採用同步的方式),雖然也可以調用BeginRead,但是其內部是通過創建一個新的線程模擬異步操作的。而這個動作是“純屬浪費,而且會影響到性能”。因此,在使用FileStream時,要一開始就要想明白是採用異步還是同步。若採用異步方式,就要調用BeginXXX,若採用同步,就採用XXX方法。這樣才能保證可以獲得最佳的性能。若在使用的過程中,既需要同步使用,又需要異步使用,那就設定FileStream爲異步吧!或者再笨的方法就是創建兩個FileStream,一個同步操作,一個異步操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章