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

深入解析 C# 委托的異步調用與跨線程操作

     編輯:關於C#

在前兩篇文章中,我們逐步探討了委托的基本概念、多路廣播以及事件機制的本質。讀完第一篇後,許多開發者心中可能已浮現一個念頭:委托不就是函數指標嗎?筆者早期也有此猜測,但缺乏文獻佐證。這系列文章實為筆者對委托機制的探索歷程,旨在透過常見用法層層遞進,幫助同樣對委托感到困惑的開發者少走彎路。

委托是函數指標的功能擴展?

近期偶然發現木頭象大神的文章《從函數指針到代理(C#代理入門)》,文中指出「委托是函數指標功能的擴展」。這一觀點頗具啟發性,姑且不論其對錯,我們可以此為切入點,探討委托相較於傳統函數指標究竟擴展了哪些功能。

在 C# 中了解對象特質極為便捷:聲明對象後輸入「.」號,智能感知即會列出其屬性與方法。對實例化的委托對象同樣適用,透過此方式可快速掌握委托特有的方法與屬性。以下重點解析幾個關鍵方法的應用。

9. 異步委托的實現

異步委托主要運用 BeginInvoke() 與 EndInvoke() 兩個方法。相較於同步執行的 Invoke(),它們具備以下特性:

  • BeginInvoke():以異步方式啟動目標方法,調用後立即返回,不會阻塞當前線程,即使目標方法耗時極長也不例外。適合用於執行耗時操作且無需立即獲取返回值的場景。
  • EndInvoke():與 BeginInvoke() 配對使用,用於檢索調用結果。若異步方法尚未完成,則會阻塞直至執行完畢。其參數為 BeginInvoke() 返回的 IAsyncResult 對象。

以下範例展示基本異步調用流程:

using System.Threading;

namespace AsyncDelegate
{
    class Program
    {
        delegate void MyDelegate();

        static void Main(string[] args)
        {
            MyDelegate ObjDelegate = new MyDelegate(ObjDelegateFun);

            // 異步調用
            IAsyncResult ObjIr = ObjDelegate.BeginInvoke(null, null);
            Console.WriteLine("委托方法執行中.");
            Thread.Sleep(5000);
            ObjDelegate.EndInvoke(ObjIr);
            Console.ReadLine();
        }

        static private void ObjDelegateFun()
        {
            Thread.Sleep(5000);
            Console.WriteLine("委托方法調用成功!");
        }
    }
}

執行時會發現「委托方法執行中...」幾乎瞬間顯示,證明 BeginInvoke() 非阻塞返回。主線程休眠 5 秒後呼叫 EndInvoke() 獲取結果,完成異步調用。

9(2)回調式異步委托

透過 AsyncCallback 實現回調模式,避免主線程主動等待:

using System.Threading;

namespace AsyncDelegate
{
    class Program
    {
        delegate void MyDelegate();

        static void Main(string[] args)
        {
            MyDelegate ObjDelegate = new MyDelegate(ObjDelegateFun);

            // 指定回調方法
            IAsyncResult ObjIr = ObjDelegate.BeginInvoke(new AsyncCallback(CallbackFun), ObjDelegate);

            Console.WriteLine("委托方法執行中.");
            Console.ReadLine();
        }

        static private void ObjDelegateFun()
        {
            Thread.Sleep(5000);
            Console.WriteLine("委托方法調用成功!");
        }

        static private void CallbackFun(IAsyncResult ar)
        {
            MyDelegate ObjDelegate = (MyDelegate)ar.AsyncState;
            ObjDelegate.EndInvoke(ar);
            Console.WriteLine("回調方法執行完畢!");
            Console.ReadLine();
        }
    }
}

此處使用 BeginInvoke() 的另一個重載,傳入回調委托與狀態對象。目標方法完成後自動觸發回調,在回調中呼叫 EndInvoke() 獲取結果。

異步委托與多線程的異同

異步委托機制與多線程有異曲同工之妙,實際執行時目標方法的線程號確與主線程不同。但經查閱資料(推薦 Kuffy Wang 的《異步委托與多線程的區別》),兩者存在細微差異:異步不等於單純開啟子線程,同步亦非專指單線程環境。

Control.Invoke 與 Delegate.Invoke 的對比

關於上一篇遺留的 Control.Invoke() 問題,筆者總結如下關鍵差異:

方法完成工作執行線程
Control.Invoke完成工作強制於 UI Thread
Control.BeginInvoke立即返回強制於 UI Thread
[delegate].Invoke完成工作呼叫 Invoke 的 Thread
[delegate].BeginInvoke立即返回新的背景 Thread

關鍵在於:無論委托或 Control,Invoke() 皆為「絕對同步」,BeginInvoke() 皆為「絕對異步」。即使在多線程環境下,同步方法會強制按單線程方式執行;異步方法則強制按多線程方式執行。

跨線程操控控件的實戰解析

以經典的跨線程更新 UI 為例,若直接在子線程中操作控件會拋出異常。安全做法是透過委托配合 Control.Invoke() 將操作封送至 UI 線程:

public void ThreadProcUnsafe()
{
    SetText();
}

private void SetText()
{
    string text = "這個控件的內容是由子線程實現的";

    if (this.textBox1.InvokeRequired)
    {
        TreadDelgate Objdelegate = new TreadDelgate(SetText);
        this.Invoke(Objdelegate, new object[] {});
        MessageBox.Show("委托方法已返回!");
    }
    else
    {
        this.textBox1.Text = text;
    }
}

執行時會發現 MessageBox.Show 在 SetText() 返回後才執行,證明 Control.Invoke() 為絕對同步。若改用 this.BeginInvoke() 則行為與 [delegate].BeginInvoke() 一致。

筆者認為,委托實例兼具「對象」與「方法」雙重特性,是實現跨線程安全調用的關鍵。我們無法直接控制方法隸屬於哪個線程,但可透過控制委托對象(在特定線程建立)間接控制掛載方法的線程歸屬。

總結

委托的核心優勢在於「兼具對象與方法二者特質」:既可作為對象傳遞、操控,又可直接調用掛載的方法。這系列探討旨在拋磚引玉,期望讀者能總結出適合自身思路的委托使用場景,看見屬於你眼中的委托真正面貌。

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