首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

.NET 非同步機制深度解析:Invoke、BeginInvoke 與 EndInvoke 實作指南(進階篇)

     編輯:關於.NET

前言:從同步邁向非同步

在上一篇文章中,我們初步認識了 .NET 透過委派實現非同步呼叫的基本概念。本文將更進一步,透過擴充 AsyncTest 類別、引入新的委派型別,並搭配 .NET Reflector 反編譯分析,深入探討 BeginInvoke / EndInvoke 這對核心方法的內部機制、參數差異、回呼模式與異常處理,最後示範如何為自訂類別新增符合 .NET 設計規範的非同步介面。

擴充範例:新增年薪計算方法

我們在 AsyncTest 中加入 YearlySalary 方法,並為其定義對應的委派 SalaryEventHandler:

public delegate string AsyncEventHandler(string name); // 對應 Hello 方法
public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus); // 對應 YearlySalary 方法

public class AsyncTest
{
    public string Hello(string name)
    {
        return "Hello:" + name;
    }

    /// 
    /// 計算一年的薪水
    /// 
    public decimal YearlySalary(decimal salary, int monthCount, decimal bonus)
    {
        Console.WriteLine("Thread ID:#{0}", Thread.CurrentThread.ManagedThreadId);
        return salary * monthCount + bonus;
    }
}

反編譯視角:委派背後的 sealed 類別

使用 .NET Reflector 觀察編譯器為兩個委派產生的類別結構:

SalaryEventHandler

public sealed class SalaryEventHandler : MulticastDelegate
{
    public SalaryEventHandler(object @object, IntPtr method) { }
    public virtual IAsyncResult BeginInvoke(decimal salary, int monthCount, decimal bonus, AsyncCallback callback, object @object) { }
    public virtual decimal EndInvoke(IAsyncResult result) { }
    public virtual decimal Invoke(decimal salary, int monthCount, decimal bonus) { }
}

AsyncEventHandler

public sealed class AsyncEventHandler : MulticastDelegate
{
    public AsyncEventHandler(object @object, IntPtr method) { }
    public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object) { }
    public virtual string EndInvoke(IAsyncResult result) { }
    public virtual string Invoke(string name) { }
}

關鍵觀察: 兩者皆繼承 MulticastDelegate,並擁有三個 virtual 方法:Invoke(同步)、BeginInvoke / EndInvoke(非同步成對)。參數差異在於前者依委派簽名動態生成,但後兩個參數 AsyncCallback 與 object state 永遠固定。

同步呼叫:Invoke 的三種等價寫法

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread ID:#{0}", Thread.CurrentThread.ManagedThreadId);
        AsyncTest test = new AsyncTest();
        SalaryEventHandler salaryDelegate = test.YearlySalary;

        // [1] 直接呼叫
        decimal v1 = test.YearlySalary(100000, 15, 100000);
        // [2] 委派直接叫用(編譯器自動轉為 Invoke)
        decimal v2 = salaryDelegate(100000, 15, 100000);
        // [3] 顯式呼叫 Invoke
        decimal v3 = salaryDelegate.Invoke(100000, 15, 100000);

        Console.WriteLine("V1:{0}, V2:{1}, V3:{2}", v1, v2, v3);
        Console.ReadLine();
    }
}

輸出顯示三者皆在同一執行緒(例如 #10),證明均為同步阻塞呼叫。委派此處扮演「代理人」角色,參數與回傳值必須與原方法一致。

非同步核心:BeginInvoke / EndInvoke 深度解析

方法職責

  • BeginInvoke:啟動非同步請求,從執行緒池取得一個執行緒執行目標方法,回傳 IAsyncResult 物件(儲存非同步操作狀態)。
  • EndInvoke:結束非同步呼叫,取得回傳值或捕獲例外。**必須成對呼叫**,否則可能造成記憶體洩漏。

參數結構對比

委派BeginInvoke 簽名EndInvoke 簽名
SalaryEventHandlerIAsyncResult BeginInvoke(decimal, int, decimal, AsyncCallback, object)decimal EndInvoke(IAsyncResult)
AsyncEventHandlerIAsyncResult BeginInvoke(string, AsyncCallback, object)string EndInvoke(IAsyncResult)

共同點:回傳 IAsyncResult、最後兩參數固定為 AsyncCallback 與 object state。EndInvoke 參數皆為 IAsyncResult,回傳型別依委派而定。

兩種非同步呼叫模式實戰

模式一:輪詢 IAsyncResult(適用於簡單等待)

class Program
{
    static IAsyncResult asyncResult;
    static void Main(string[] args)
    {
        AsyncTest test = new AsyncTest();
        SalaryEventHandler dele = test.YearlySalary;
        asyncResult = dele.BeginInvoke(100000, 15, 100000, null, null);
        decimal val = GetResult();
        Console.WriteLine(val);
        Console.ReadLine();
    }
    static decimal GetResult()
    {
        AsyncResult result = (AsyncResult)asyncResult;
        SalaryEventHandler salDel = (SalaryEventHandler)result.AsyncDelegate;
        return salDel.EndInvoke(asyncResult);
    }
}

模式二:回呼函式(推薦,不阻塞主執行緒)

class Program
{
    static void Main(string[] args)
    {
        AsyncTest test = new AsyncTest();
        SalaryEventHandler dele = test.YearlySalary;
        dele.BeginInvoke(100000, 15, 100000, GetResultCallBack, null);
        Console.ReadLine();
    }
    static void GetResultCallBack(IAsyncResult asyncResult)
    {
        AsyncResult result = (AsyncResult)asyncResult;
        SalaryEventHandler salDel = (SalaryEventHandler)result.AsyncDelegate;
        decimal val = salDel.EndInvoke(asyncResult);
        Console.WriteLine(val);
    }
}

傳遞自訂狀態物件(state 參數)

dele.BeginInvoke(100000, 15, 100000, GetResultCallBack, 2000); // 傳入 int
static void GetResultCallBack(IAsyncResult asyncResult)
{
    int para = (int)asyncResult.AsyncState; // 取回 2000
    Console.WriteLine(para);
}

非同步異常處理:EndInvoke 是關鍵

若目標方法拋出例外,CLR 會在 EndInvoke 時重新擲出原始例外:

public decimal YearlySalary(decimal salary, int monthCount, decimal bonus)
{
    throw new Exception("error"); // 模擬錯誤
    return salary * monthCount + bonus;
}
static void GetResultCallBack(IAsyncResult asyncResult)
{
    try
    {
        decimal val = salDel.EndInvoke(asyncResult); // 這裡捕獲
        Console.WriteLine(val);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message); // 輸出 "error"
    }
}

為自訂類別新增標準非同步介面(EAP 模式前身)

遵循 .NET 命名慣例:Begin+方法名 / End+方法名。

public class AsyncTest
{
    private delegate string AsyncEventHandler(string name);
    private AsyncEventHandler _Async;

    public string Hello(string name) => "Hello:" + name;

    // BeginHello:參數同 Hello,補上 AsyncCallback & object state,回傳 IAsyncResult
    public virtual IAsyncResult BeginHello(string name, AsyncCallback callback, object state)
    {
        AsyncEventHandler del = Hello;
        this._Async = del;
        return del.BeginInvoke(name, callback, state);
    }

    // EndHello:參數為 IAsyncResult,回傳 string
    public virtual string EndHello(IAsyncResult asyncResult)
    {
        if (asyncResult == null) throw new ArgumentNullException(nameof(asyncResult));
        if (this._Async == null) throw new ArgumentException("_Async");
        string val = string.Empty;
        try { val = this._Async.EndInvoke(asyncResult); }
        finally { this._Async = null; }
        return val;
    }
}

用戶端呼叫自訂非同步方法

class Program
{
    static void Main(string[] args)
    {
        AsyncTest test = new AsyncTest();
        AsyncCallback callback = new AsyncCallback(OnHelloCallback);
        test.BeginHello("Andy Huang", callback, test);
        Console.ReadLine();
    }
    static void OnHelloCallback(IAsyncResult asyncResult)
    {
        AsyncTest obj = (AsyncTest)asyncResult.AsyncState;
        string val = obj.EndHello(asyncResult);
        Console.WriteLine(val);
    }
}

總結

本文透過反編譯與實戰範例,完整展示了 .NET 委派型別的非同步機制內幕:Invoke 為同步阻塞,BeginInvoke/EndInvoke 成對實現真正的非同步執行緒池調度,支援輪詢與回呼兩種模式,並能透過 AsyncState 傳遞上下文、在 EndInvoke 統一處理例外。最後示範如何為自己的類別封裝符合框架規範的非同步介面,為後續進階非同步模式(APM、EAP、TAP)奠定基礎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved