PEP 492 -- Coroutines with async and await syntax 翻譯

来源:https://www.cnblogs.com/zhaof/archive/2018/12/03/10024264.html
-Advertisement-
Play Games

因為工作中慢慢開始用python的協程,所以想更好的理解一下實現方式,故翻譯此文 原文中把辭彙表放到最後,但是我個人覺得放在最開始比較好,這樣可以增加當你看原文時的理解程度 辭彙表 原生協程函數 Native coroutine function: 由async def定義的協程函數,可以使用awa ...


因為工作中慢慢開始用python的協程,所以想更好的理解一下實現方式,故翻譯此文

原文中把辭彙表放到最後,但是我個人覺得放在最開始比較好,這樣可以增加當你看原文時的理解程度

辭彙表

原生協程函數 Native coroutine function:

由async def定義的協程函數,可以使用await和return value語句

 

原生協程 Native coroutine:

原生協程函數返回的對象。見“await表達式”一節。

 

基於生成器的協程函數 Generator-based coroutine function:

基於生成器語法的協程,最常見的是用 @asyncio.coroutine裝飾過的函數。

 

基於生成器的協程 Generator-based coroutine:

基於生成器的協程函數返回的對象。

 

協程 Coroutine:

“原生協程”和“基於生成器的協程”都是協程。

 

協程對象 Coroutine object:

“原生協程對象”和“基於生成器的協程對象”都是協程對象。

 

Future-like對象 Future-like object:

一個有__await__方法的對象,或一個有tp_as_async->am_await函數的C語言對象,它們返回一個迭代器。Future-like對象可以在協程里被一條await語句消費(consume)。協程會被await語句掛起,直到await語句右邊的Future-like對象的__await__執行完畢、返回結果。見“await表達式”一節。

 

Awaitable

一個Future-like對象或一個協程對象。見“await表達式”一節。

 

非同步上下文管理器 Asynchronous context manager:

有__aenter__和__aexit__方法的對象,可以被async with語句使用。見“非同步上下文管理器和‘async with’”一節。

 

可非同步迭代對象 Asynchronous iterable:

有__aiter__方法的對象, 該方法返回一個非同步迭代器對象。可以被async for語句使用。見“非同步迭代器和‘async for’”一節。

 

非同步迭代器 Asynchronous iterator:

有__anext__方法的對象。見“非同步迭代器和‘async for’”一節。

摘要

隨著互聯網和連接程式的增長,引發了對響應性和可擴展代碼的需求,該提議的目標是讓我們共容易的通過編寫顯示非同步,高併發的python代碼並且更加Pythonic

它提出把寫成的概念獨立出來,並引入新的支持語法。最終的目標是幫助在python中建立一個通用的,易於接近的非同步編程構思模型,並使其儘可能接近於同步編程(說白了就是讓你通過類似寫同步編程的方式,寫出非同步代碼)

 

這個PEPE建設非同步任務是類似於標準模塊asyncio.events.AbstractEventLoop的事件迴圈調度和協調。雖然這個PEP不依賴人去特定的時間迴圈實現,但它僅僅與使用yield作為調度程式信號的協程類型相關,表示協程將等待知道事件(例如:IO)完成

我們相信,這裡提出的更改將有助於python在快速增長的非同步編程領域保持更好的競爭力,因為許多其他語言已經採或將要採用類似的功能

API設計和實施修訂

對Python 3.5的初始beta版本的反饋導致重新設計支持此PEP的對象模型,以更清楚地將原生協程與生成器分離 - 而不是一種新的生成器,現在原生協程有明確的獨立類型

這個改變主要是為瞭解決原生協程在tornado里使用出現的一些問題

 

在CPython3.5.2 中更新了__aiter__ 協議。

在3.5.2之前,__aiter__ 是被期望返回一個等待解析為非同步迭代器,從3.5.2開始,__aiter__ 應該直接返回非同步迭代器

如果在3.5.2中使用舊協議中,Python將引發PendingDeprecationWarning異常

在CPython 3.6中,舊的__aiter__協議仍將受到引發DeprecationWarning的支持

在CPython 3.7中,將不再支持舊的__aiter__協議:如果__aiter__返回除非同步迭代器之外的任何內容,則將引發RuntimeError。

 

理論和目標

當前的Python支持通過生成器(PEP342)實現協程,並通過PEP380中引入的yield from 語法進一步增強,這種方法有很多缺點:

  • 協程式與生成器具有相同的語法,很容易混淆,對於初級開發者來說尤其如此。
  • 一個函數是否是一個協程,取決於它裡面是否出現了yield或yield from語句。這並不明顯,容易在重構函數的時候搞亂,導致出錯。
  • 非同步調用被yield語法限制了,我們不能獲得、使用更多的語法特性,比如with和for。

這個PEP把協程從生成器獨立出來,成為Python的一個原生事物。這會消除協程和生成器之間的混淆,方便編寫不依賴特定庫的協程代碼。也為linter和IDE進行代碼靜態分析提供了機會。

使用原生協程和相應的新語法,我們可以在非同步編程時使用上下文管理器(context manager)和迭代器。如下文所示,新的async with語句可以在進入、離開運行上下文(runtime context)時進行非同步調用,而async for語句可以在迭代時進行非同步調用。

 

規範

該提議引入了新的語法和語義來增強Python對協程支持。

請理解Python現有的協程(見PEP 342和PEP 380),這次改變的動機來自於asyncio框架(PEP 3156)和Confunctions提案(PEP 3152,此PEP已經被廢棄)。

由此,在本文中,我們使用“原生協程”指用新語法聲明的協程。“生成器實現的協程”指用傳統方法實現的協程。“協程”則用在兩個都可以使用的地方。

新的協程聲明語法

使用以下語法聲明原生協程:

async def read_data(db):
    pass

協程語法的關鍵點:

  • async def函數必定是協程,即使裡面不含有await語句。
  • 如果在async函數裡面使用yield或yield from語句,會引發SyntaxError異常。
  • 在CPython內部,引入兩個新的代碼對象標識(code object flags):
    CO_COROUTINE表示這是原生協程。(由新語法定義)
    CO_ITERABLE_COROUTINE表示這是用生成器實現的協程,但是和原生協程相容。(用裝飾器types.coroutine()裝飾過的生成器協程)
  • 調用一個普通生成器,返回一個生成器對象(generator object);相應的,調用一個協程返回一個協程對象(coroutine object
  • 協程不再拋出StopIteration異常,因為拋出的StopIteration異常會被包裝(wrap)成一個RuntimeError異常。對於普通的生成器想要這樣需要進行future import
  • 如果一個協程從未await等待就被垃圾收集器銷毀了,會引發一個RuntimeWarning異常

types.coroutine()

types模塊添加了一個新函數coroutine(fn),使用它,“生成器實現的協程”和“原生協程”之間可以進行互操作。 

@types.coroutine
def process_data(db):
    data = yield from read_data(db)
    ...

該函數將CO_ITERABLE_COROUTINE標誌應用於生成器函數的代碼對象,使其返回一個協程對象。如果fn不是生成器函數,它將被包裝。如果它返回一個生成器,它將被包裝在一個等待的代理對象中(參見下麵的等待對象的定義)。

types.coroutine()不會設置CO_COROUTINE標識,只有用新語法定義的原生協程才會有這個標識。

await表達式

新的await表達式用於獲得協程執行的結果:

async def read_data(db):
    data = await db.fetch('SELECT ...')
    ...

await 和yield from 是非常類似的,會掛起read_data的執行,直到等待db.fetch完成並返回結果數據。

await使用yield from的實現,但是加入了一個額外步驟——驗證它的參數類型。await只接受awaitable對象,awaitable對象是以下的其中一個:

  • 一個原生協程對象(由一個原生協程函數返回)
  • 用裝飾器types.coroutine()裝飾的一個“生成器實現的協程”對象
  • 一個有__await__方法的對象(__await__方法返回的一個迭代器)。調用鏈上的每一個yield from 最終都會以一個yield結束,這是Future實現的基本機制。在Python內部,協程是一種特殊的生成器,所以每個await最終會被await調用鏈條上的某個yield語句掛起。為了讓協程也有這樣的行為,添加了一個新的魔術方法__await__。

    例如,在asyncio模塊,要想在await語句里使用Future對象,唯一的修改是給asyncio.Future加一行:__await__ = __iter__

在本文中,有__await__方法的對象被稱為Future-like對象(協程會被await語句掛起,直到await語句右邊的Future-like對象的__await__執行完畢、返回結果。)

如果__await__返回的不是一個迭代器,則引發TypeError異常。 

在CPython C API,有tp_as_async.am_await函數的對象,該函數返回一個迭代器(類似__await__方法)

如果在async def函數之外使用await語句,會引發SyntaxError異常。這和在def函數之外使用yield語句一樣。

如果await右邊不是一個awaitable對象,會引發TypeError異常。

 

新的運算符優先順序表

有效的語法示例

 

ExpressionWill be parsed as
if await fut: pass if (await fut): pass
if await fut + 1: pass if (await fut) + 1: pass
pair = await fut, 'spam' pair = (await fut), 'spam'
with await fut, open(): pass with (await fut), open(): pass
await foo()['spam'].baz()() await ( foo()['spam'].baz()() )
return await coro() return ( await coro() )
res = await coro() ** 2 res = (await coro()) ** 2
func(a1=await coro(), a2=0) func(a1=(await coro()), a2=0)
await foo() + await bar() (await foo()) + (await bar())
-await foo() -(await foo())

 

 

 

 

 

 

 

 

 

 

 

無效的用法

 

ExpressionShould be written as
await await coro() await (await coro())
await -coro() await (-coro())
 

 

 

 

非同步上下文管理器和“async with”

 

非同步上下文管理器(asynchronous context manager),可以在它的enter和exit方法里掛起、調用非同步代碼。

為此,我們設計了一套方案,添加了兩個新的魔術方法:__aenter__和__aexit__,它們必須返回一個awaitable。

非同步上下文管理器的一個示例:

class AsyncContextManager:
    async def __aenter__(self):
        await log('entering context')

    async def __aexit__(self, exc_type, exc, tb):
        await log('exiting context')

 

新語法

採納了一個非同步上下文管理器的新語法

async with EXPR as VAR:
    BLOCK

 

這在語義上等同於:

mgr = (EXPR)
aexit = type(mgr).__aexit__
aenter = type(mgr).__aenter__(mgr)

VAR = await aenter
try:
    BLOCK
except:
    if not await aexit(mgr, *sys.exc_info()):
        raise
else:
    await aexit(mgr, None, None, None)

與常規with語句一樣,可以在單個async with語句中指定多個上下文管理器。

在使用async with時,如果上下文管理器沒有__aenter__和__aexit__方法,則會引發錯誤。在async def函數之外使用async with則會引發SyntaxError異常。

例子

使用非同步上下文管理器,可以輕鬆地為協同程式實現適當的資料庫事務管理器:

async def commit(session, data):
    ...

    async with session.transaction():
        ...
        await session.update(data)
        ...

 

加鎖的處理也更加簡潔

async with lock:
    ...

而不再是:

with (yield from lock):
    ...

非同步迭代器和“async for”

非同步迭代器可以在它的iter實現里掛起、調用非同步代碼,也可以在它的__next__方法里掛起、調用非同步代碼。要支持非同步迭代,需要:

  • 對象必須實現__aiter__方法(或者,如果使用CPython C API,需要定義tp_as_async.am_aiter)返回一個非同步迭代器對象
  • 一個非同步迭代對象必須實現一個__anext__方法(或者,如果使用CPython C API,需要定義tp_as_async.am_anext)返回一個awaitable
  • 要停止迭代,__anext__必須拋出一個StopAsyncIteration異常。

一個一步迭代的例子:

class AsyncIterable:
    def __aiter__(self):
        return self

    async def __anext__(self):
        data = await self.fetch_data()
        if data:
            return data
        else:
            raise StopAsyncIteration

    async def fetch_data(self):
        ...

新語法

採納了一個迭代非同步迭代器的新語法:

async for TARGET in ITER:
    BLOCK
else:
    BLOCK2

在語義上等同於:

iter = (ITER)
iter = type(iter).__aiter__(iter)
running = True
while running:
    try:
        TARGET = await type(iter).__anext__(iter)
    except StopAsyncIteration:
        running = False
    else:
        BLOCK
else:
    BLOCK2

如果async for的迭代器不支持__aiter__方法,則引發TypeError異常。如果在async def函數外使用async for,則引發SyntaxError異常。

和普通的for語句一樣,async for有一個可選的else分句。

例子1

使用非同步迭代協議,可以在迭代期間非同步緩衝數據:

async for data in cursor:
    ...

其中cursor是一個非同步迭代器,它在每N次迭代後從資料庫中預取N行數據。

以下代碼說明瞭新的非同步迭代協議:

class Cursor:
    def __init__(self):
        self.buffer = collections.deque()

    async def _prefetch(self):
        ...

    def __aiter__(self):
        return self

    async def __anext__(self):
        if not self.buffer:
            self.buffer = await self._prefetch()
            if not self.buffer:
                raise StopAsyncIteration
        return self.buffer.popleft()

然後,可以這樣使用Cursor類

async for row in Cursor():
    print(row)

與下述代碼相同:

i = await Cursor().__aiter__()
while True:
    try:
        row = await i.__anext__()
    except StopAsyncIteration:
        break
    else:
        print(row)

例子2:

以下是將常規迭代轉換為非同步迭代的實用程式類。雖然這不是一件非常有用的事情,但代碼說明瞭常規迭代器和非同步迭代器之間的關係。

class AsyncIteratorWrapper:
    def __init__(self, obj):
        self._it = iter(obj)

    def __aiter__(self):
        return self

    async def __anext__(self):
        try:
            value = next(self._it)
        except StopIteration:
            raise StopAsyncIteration
        return value

async for letter in AsyncIteratorWrapper("abc"):
    print(letter)

為什麼是StopAsyncIteration?

協程在內部仍然是基於生成器實現的,因此,在PEP479之前,下麵兩者是沒有區別的

def g1():
    yield from fut
    return 'spam'

def g2():
    yield from fut
    raise StopIteration('spam')

由於PEP 479已被正式採納,並作用於協程,以下代碼的StopIteration會被包裝(wrapp)成一個RuntimeError。

async def a1():
    await fut
    raise StopIteration('spam')

所以,要想通知外部代碼迭代已經結束,拋出一個StopIteration異常的方法不行了。因此,添加了一個新的內置異常StopAsyncIteration,用於表示迭代結束。

此外,根據PEP 479,協程拋出的所有StopIteration異常都會被包裝成RuntimeError異常。

協程對象

和生成器的不同之處

本節僅適用於具有CO_COROUTINE的原生協程,即使用新的async def 定義的函數

對於asyncio模塊里現有的“基於生成器的協程”,仍然保持不變。

為了把協程和生成器的概念區分開來:

  1. 原生協程對象不實現__iter__和__next__方法,因此,不能對其進行迭代(如for...in迴圈),也不能傳遞給iter(),list(),tuple()及其它內置函數。如果嘗試對其使用__iter__或__next__方法,會引發TypeError異常。
  2. 未裝飾的生成器不能yield from一個原生協程,這樣做會引發TypeError異常。
  3. “基於生成器的協程”在經過 @asyncio.coroutine裝飾後,可以yield from原生協程對象。
  4. 對於原生協程對象和原生協程函數,調用inspect.isgenerator()和inspect.isgeneratorfunction()會返回False。

協程對象的方法

協程是基於生成器實現的,因此它們有共同的代碼。像生成器對象那樣,協程也有throw(),send()和close()方法。
對於協程,StopIteration和GeneratorExit起著同樣的作用(雖然PEP 479已經應用於協程)。詳見PEP 342、PEP 380,以及Python文檔。

對於協程,send(),throw()方法用於往Future-like對象發送內容、拋出異常。

調試特性

初級開發者在使用協程時可能忘記使用yield from語句,比如:

@asyncio.coroutine
def useful():
    asyncio.sleep(1) # this will do nothing without 'yield from'

 

為了調試這種錯誤,在asyncio中有一個特殊的調試模式,其中@coroutine裝飾器用一個特殊對象包裝所有函數,並使用析構函數記錄警告。每當一個包裝的生成器被垃圾回收時,就會生成一條詳細的日誌消息,其中包含有關定義裝飾器函數的確切位置,堆棧跟蹤收集位置等的信息.Wrapper對象還提供了一個方便的__repr__函數,其中包含有關生成器的詳細信息。

新標準庫函數

  • types.coroutine(gen) 詳見types.coroutine()一節。
  • inspect.iscoroutine(obj) 如果obj是原生協程對象,返回True。
  • inspect.iscoroutinefunction(obj) 如果obj是原生協程函數,返回True。
  • inspect.isawaitable(obj) 如果obj是awaitable返回True。
  • inspect.getcoroutinestate(coro) 返回原生協程對象的當前狀態(inspect.getfgeneratorstate(gen)的鏡像)。
  • inspect.getcoroutinelocals(coro) 返回一個原生協程對象的局部變數的映射【譯註:變數名->值】(inspect.getgeneratorlocals(gen) 的鏡像)。
  • sys.set_coroutine_wrapper(wrapper) 允許攔截原生協程對象的創建。wrapper必須是一個接受一個參數callable(一個協程對象),或者是None。None會重置(reset)這個wrapper。如果再次調用,新的wrapper會取代舊的。這個函數是線程專有的(thread-specific)。詳見“調度特性”一節。
  • sys.get_coroutine_wrapper() 返回當前的包裝對象(wrapper object)。如果沒有則返回None。這個函數是線程專有的(thread-specific)。詳見“調度特性”一節。

新的抽象基類

為了更好地與現有框架(如Tornado,見[13])和編譯器(如Cython,見[16])集成,增加了兩個新的抽象基類(ABC):

  1. collections.abc.Awaitable,Future-like類的抽象基類,實現__await__方法。
  2. collections.abc.Coroutine,協程對象的抽象基類,實現send(value),throw(type, exc, tb),close()和__await__()方法。

註意,“基於生成器的協程”(有CO_ITERABLE_COROUTINE標識)並不實現__await__方法,因此它們不是collections.abc.Coroutine和collections.abc.Awaitable的實例:

@types.coroutine
def gencoro():
    yield

assert not isinstance(gencoro(), collections.abc.Coroutine)

# however:
assert inspect.isawaitable(gencoro())

為了便於測試對象是否支持非同步迭代,還添加了兩個ABC:

  1. collections.abc.AsyncIterable --用於測試__aiter__方法。
  2. collections.abc.AsyncIterator --用於測試__aiter__和__anext__方法。

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 外觀模式又稱為門面模式Facade是一種簡單的設計模式,但是他背後的思想為迪米特原則,理解門面模式更有助於理解迪米特原則--不要和陌生人說話的原則,可以降低系統的耦合程度,本文介紹了外觀模式的意圖,結構,並且給出了java代碼示例。 ...
  • 1、編寫裝飾器,為多個函數加上認證的功能(用戶的賬號密碼來源於文件)要求登錄成功一次,後續的函數都無需再輸入用戶名和密碼 2、編寫裝飾器,為多個函數加上記錄調用功能,要求每次調用函數都將被調用的函數名稱寫入文件 進階練習:1.編寫下載網頁內容的函數,要求功能是:用戶傳入一個url,函數返回下載頁面的 ...
  • 首先併發編程有三大特性: 可見性,有序性,原子性。volatile關鍵字實現了前面兩個特性。那麼它是如何實現這兩個特性的呢? 首先是可見性。可見性主要是讓緩存,直接寫穿透到主存中。然後另外的cpu 通過底層的硬體層面的嗅探,可以發現自己cpu本地的緩存已經失效。然後到主存中直接讀取。現在讓我們來看看 ...
  • 每種語言都會有字元串的操作,因為字元串是我們平常開發使用頻率最高的一種類型。今天我們來聊一下Java的字元串操作及在某些具體方法中與C#的不同,對於需要熟悉多種語言的人來說,作為一種參考。進行誡勉 首先,什麼是字元串? 字元串是字元的序列,是作為一種對象而存在。說的直白點,字元串就是一些字元的組合, ...
  • 拷貝控制 右值與const引用 背景:當一個函數的返回值是自定義類型時,調用側用什麼類型接收?? 1,如果自定義類型的拷貝構造函數的參數用const修飾了:可以用下麵的方式接收。 2,如果自定義類型的拷貝構造函數的參數沒有用const修飾了:必須用下麵的方式接收 編譯錯誤: 解釋: 第一種條件下,用 ...
  • 值傳遞: (形式參數類型是基本數據類型和String):方法調用時,實際參數把它的值傳遞給對應的形式參數,形式參數只是用實際參數的值初始化自己的存儲單元內容,是兩個不同的存儲單元,所以方法執行中形式參數值的改變不影響實際參數的值。 引用傳遞: (形式參數類型是引用數據類型參數除去String):也稱 ...
  • 學習Java一年多了,練習了很多,這條路真的很難走.還有半年多畢業的我整理整理所學習的筆記給大家分享主要也是讓自己記憶加深.自學時用到的時阿發老師的教學視頻,通俗易懂還有題庫可以練習.最經典的就是阿發老師會教你如何開發車而不是使用車.筆記中的截圖和源碼都是老師的.這次整理主要是給自己看的,有需要的朋 ...
  • 《工作細胞》最近比較火,bilibili 上目前的短評已經有17000多條。 先看分析下頁面 右邊 li 標簽中的就是短評信息,一共20條。一般我們載入大量數據的時候,都會做分頁,但是這個頁面沒有,只有一個滾動條。 隨著滾動條往下拉,信息自動載入了,如下圖,變40條了。由此可見,短評是通過非同步載入的 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...