[轉] Linux Asynchronous I/O Explained

来源:https://www.cnblogs.com/xuyaowen/archive/2019/04/09/linux-aio.html
-Advertisement-
Play Games

Linux Asynchronous I/O Explained (Last updated: 13 Apr 2012) ******************************************************************************* by Vasily ...


Linux Asynchronous I/O Explained		    (Last updated: 13 Apr 2012)
*******************************************************************************
				by Vasily Tarasov <tarasov AT vasily dot name>

Asynchronoes I/O (AIO) is a method for performing I/O operations so that the
process that issued an I/O request is not blocked till the data is available.
Instead, after an I/O request is submitted, the process continues to execute
its code and can later check the status of the submitted request.

Linux kernel provides only *5* system calls for performing asynchronoes I/O.
Other AIO functions commonly descibed in the literature are implemented in the
user space libraries and use the system calls internally. Some libraries can
also emulate AIO functionality entirely in the user space without any kernel
support.

There are two main libraries in Linux that facilitate AIO, we will refer to
them as *libaio* and *librt* (the latter one is a part of libc).

In this text, I first discuss system calls, then libaio, and finaly librt.

AIO System Calls
*******************************************************************************
						based on Linux 3.2.1 kernel

AIO system call entry points are located in "fs/aio.c" file in the kernel's
source code.  Types and constants exported to the user space reside in
"/usr/include/linux/aio_abi.h" header file.

There are only 5 AIO system calls:

* int io_setup(unsigned nr_events, aio_context_t *ctxp);

* int io_destroy(aio_context_t ctx);

* int io_submit(aio_context_t ctx, long nr, struct iocb *cbp[]);

* int io_cancel(aio_context_t ctx, struct iocb *, struct io_event *result);

* int io_getevents(aio_context_t ctx, long min_nr, long nr,
			struct io_event *events, struct timespec *timeout);

I will demonstrate the usage of these system calls using a sequence of programs
in the increasing order of their complexity.

Program 1:

>> snip start: 1.c  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

00  #define _GNU_SOURCE		/* syscall() is not POSIX */
01 
02  #include <stdio.h>		/* for perror() */
03  #include <unistd.h>		/* for syscall() */
04  #include <sys/syscall.h>	/* for __NR_* definitions */
05  #include <linux/aio_abi.h>	/* for AIO types and constants */
06 
07  inline int io_setup(unsigned nr, aio_context_t *ctxp)
08  {
09  	return syscall(__NR_io_setup, nr, ctxp);
10  }
11  
12  inline int io_destroy(aio_context_t ctx) 
13  {
14  	return syscall(__NR_io_destroy, ctx);
15  }
16  
17  int main()
18  {
19  	aio_context_t ctx;
20  	int ret;
21  
22  	ctx = 0;
23  
24  	ret = io_setup(128, &ctx);
25  	if (ret < 0) {
26  		perror("io_setup error");
27  		return -1;
28  	}
29  
30  	ret = io_destroy(ctx);
31  	if (ret < 0) {
32  		perror("io_destroy error");
33  		return -1;
34  	}
35  
36  	return 0;
37  }

<< snip end: 1.c  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

For now, ignore first 17 lines of the code and look at main() function.  In line
24 we call io_setup() system call to create so called "AIO context" in the
kernel. AIO context is a set of data structures that the kernel supports to
perform AIO.  Every process can have multiple AIO contextes and as such one
needs an identificator for every AIO context in a process (XXX: come up with a
handy example how it can be used). Ctx variable of type aio_context_t defined in
line 19 stores such an identificator in our example.  A pointer to ctx variable
is passed to io_setup() as a second argument and kernel fills this variable
with a context identifier. Interestingly, aio_context_t is actually just an
unsigned long defined in the kernel ("linux/aio_abi.h") like that:

typedef unsigned long aio_context_t;

In line 22 we set ctx to 0 which is required by kernel or io_setup() fails with
-EINVAL error.

The first argument of io_setup() function - 128 in our case - is the maximum
number of requests that can simultaneously reside in the context. This will be
explained in more details in the next examples.

In line 30 we destroy just created AIO context by calling io_destroy() system
call with ctx as an argument.

The lines above 17 are just helpers that allow to call system calls directly. We
use glibc's syscall() function that invokes any system call by its number.  It
is only required if one wants to call system calls directly without using AIO
libraries'  wrapper functions (provided by libaio and librt). Notice, that
syscall() functions's return value follows the usual conventions for indicating
an error: -1, with errno set to a positive value that indicates the error.
So, we check if the values returned by io_setup() and io_destroy() are less than
zero to detect the error, and then use perror() function that will print the
errno.

In the last example we did a minimal thing: created an AIO context and then
destroyed it immediatelly.  In the next example we submit one request to the
context and then query its status later.

Program 2:

>> snip start: 2.c  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

00  #define _GNU_SOURCE		/* syscall() is not POSIX */
01 
02  #include <stdio.h>		/* for perror() */
03  #include <unistd.h>		/* for syscall() */
04  #include <sys/syscall.h>	/* for __NR_* definitions */
05  #include <linux/aio_abi.h>	/* for AIO types and constants */
06  #include <fcntl.h>		/* O_RDWR */
07  #include <string.h>		/* memset() */
08  #include <inttypes.h>	/* uint64_t */
09  
10  inline int io_setup(unsigned nr, aio_context_t *ctxp)
11  {
12  	return syscall(__NR_io_setup, nr, ctxp);
13  }
14  
15  inline int io_destroy(aio_context_t ctx) 
16  {
17  	return syscall(__NR_io_destroy, ctx);
18  }
19  
20  inline int io_submit(aio_context_t ctx, long nr,  struct iocb **iocbpp) 
21  {
22  	return syscall(__NR_io_submit, ctx, nr, iocbpp);
23  }
24  
25  inline int io_getevents(aio_context_t ctx, long min_nr, long max_nr,
26  		struct io_event *events, struct timespec *timeout)
27  {
28  	return syscall(__NR_io_getevents, ctx, min_nr, max_nr, events, timeout);
29  }
30  
31  int main()
32  {
33  	aio_context_t ctx;
34  	struct iocb cb;
35  	struct iocb *cbs[1];
36  	char data[4096];
37  	struct io_event events[1];
38  	int ret;
39  	int fd;
40  
41  	fd = open("/tmp/testfile", O_RDWR | O_CREAT);
42  	if (fd < 0) {
43  		perror("open error");
44  		return -1;
45  	}
46  
47  	ctx = 0;
48  
49  	ret = io_setup(128, &ctx);
50  	if (ret < 0) {
51  		perror("io_setup error");
52  		return -1;
53  	}
54  
55  	/* setup I/O control block */
56  	memset(&cb, 0, sizeof(cb));
57  	cb.aio_fildes = fd;
58  	cb.aio_lio_opcode = IOCB_CMD_PWRITE;
59
60	/* command-specific options */
61  	cb.aio_buf = (uint64_t)data;
62  	cb.aio_offset = 0;
63  	cb.aio_nbytes = 4096;
64  
65  	cbs[0] = &cb;
66  
67  	ret = io_submit(ctx, 1, cbs);
68  	if (ret != 1) {
69		if (ret < 0)
70	  		perror("io_submit error");
71		else
72			fprintf(stderr, "could not sumbit IOs");
73		return  -1;
74	}
75
76	/* get the reply */
77	ret = io_getevents(ctx, 1, 1, events, NULL);
78	printf("%d\n", ret);
79
80	ret = io_destroy(ctx);
81	if (ret < 0) {
82		perror("io_destroy error");
83		return -1;
84	}
85  
86 	return 0;
87  }

<< snip end: 2.c  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

Every I/O request that is submitted to an AIO context is represented by an I/O
control block structure - struct iocb - declared in line 34.  We initialize this
structure in lines 55-63. First, the whole structure is zeroed, then file
descriptor (aio_fildes) and command (aio_lio_opcode) fields are set.

File descriptor corresponds to a previously opened file, in our example we
open "/tmp/testfile" file in line 41.

AIO commands currently supported by Linux kernel are:

IOCB_CMD_PREAD
   positioned read; corresponds to pread() system call.

IOCB_CMD_PWRITE
   positioned write; corresponds to pwrite() system call.

IOCB_CMD_FSYNC
   sync file's data and metadata with disk; corresponds to fsync() system call.

IOCB_CMD_FDSYNC
   sync file's data and metadata with disk, but only metadata needed to access
   modified file data is written; corresponds to fdatasync() system call.

IOCB_CMD_PREADV
   vectored positioned read, sometimes called "scattered input";
   corresponds to pread() system call.

IOCB_CMD_PWRITEV
   vectored positioned write, sometimes called "gathered output";
   corresponds to pwrite() system call.

IOCB_CMD_NOOP
   defined in the header file, but is not used anywhere else in the kernel.

The semantics of other fields in the iocb structure depends on the command
specified. For now, we will limit our discussion to IOCB_CMD_PREAD and
IOCB_CMD_PWRITE commands. After understanding AIO interface for these two
commands, we will look into the remaining ones.

In lines 60-63 of our running example we set command-specific fields of iocb
structure: aio_buf and aio_nbytes corresond to a region in memory to which
data should be read or written to;  aio_offset is an absolute offset in a file.

Now, when one I/O control block is ready, we put a pointer to it in an array
(line 65) and then pass this array to the io_submit() system call (line 67).
io_submit() takes AIO context ID, size of the array and the array itself as the
arguments. Notice, that array should contain *pointers* to the iocb structures,
not the structures themself.

io_submit()'s return code can be one of the following values:

A) ret = (number of iocbs sumbmitted)
	Ideal case, all iocbs were accepted for processing.

B) 0 < ret <  (number of iocbs sumbmitted)
	io_submit() system call processes iocbs one by one starting from
	the first entry in the passed array. If submission of some iocb fails,
	it stops at this point and returns the index of iocb that failed.
	There is no way to know what is the exact reason of a failure.
	However, if the very first iocb submission fails, see point C.

C) ret < 0
	There are two reasons why this could happen:
	1) Some error happened even before io_submit() started to iterate
	   over iocbs in the array (e.g., AIO context was invalid). 
	2) The submission of the very first iocb (cbx[0]) failed).

So, in our example, we handle io_submit()'s return code in an unusual way. If
return code is not equal to the number of iocbs, then that is a clear error but
we don't know its reason (errno is not set). Consequently, we use
fprintf(stderr, ...) function to print error notification on the screen.
Otherwise, if return code is less than zero, then we know the error (errno is
set) and use perror() function instead.  Notice, that in case of a single iocb
in the array (as in our example) such a complex error handling makes less sense:
if the first (and only) iocb fails, we are guaranteed to get an error
information (see point C above). We handle error in a more complex way in this
example only to reuse the same code later, when we submit multiple iocbs in a
single io_submit() call.

After iocb is submitted we can perform any other actions without waiting for I/O
to complete. For every completed  I/O request (successfully or unsuccessfully)
kernel creates an io_event structure. To obtain the list of io_events (and
consequently all completed iocbs) io_getevent() system call should be used (line
77). When calling io_getevents(), one needs to specify:

a) which AIO context to get events from (ctx variable)

b) a buffer where the kernel should load events to (events varaiable)

c) minimal number of events one wants to get (first 1 in our program).
   If less then this number of iocbs are currently completed,
   io_getevents() will block till enough events appear. See point e)
   for more details on how to control blocking time.
     
d) maximum number of events one wants to get. This usually is
   the size of the events buffer (second 1 in our program)

e) If not enough events are available, we don't want to wait forever.
   One can specify a relative deadline as the last argument.
   NULL in this case means to wait infinitely.
   If one wants io_getevents() not to block at all then 
   timespec timeout structure need to be initialzed to zero
   seconds and zero nanoseconds.

The return code of io_getevents can be:

A) ret = (max number of events)
	All events that fit in the user provided buffer were obtained
	from the kernel. There might be more pending events in the kernel.
B) (min number of events) <= ret <= (max number of events)
	All currently available events were read from the kernel and no
	blocking happened.
C) 0 < ret < (min number of events)
	All currently available events were read from the kernel and
	we blocked to wait for the time user has specified.
E) ret = 0
	no events are available XXX:? does blocking happen in this case?..

F) ret < 0
	an error happened


TO BE CONTINUED...


/proc/sys/fs/aio-max-nr
/proc/sys/fs/aio-nr

Note that timeout is relative and will be updated if not NULL and the operation
blocks

Check how vectors a provide to vectored PREADV and PWRITEV commands.

Other fields to fill/explain:

        /* these are internal to the kernel/libc. */
        __u64   aio_data;       /* data to be returned in event's data */
        __u32   PADDED(aio_key, aio_reserved1);
                                /* the kernel sets aio_key to the req # */

        /* common fields */
+++     __u16   aio_lio_opcode; /* see IOCB_CMD_ above */
        __s16   aio_reqprio;
        __u32   aio_fildes;

        __u64   aio_buf;
        __u64   aio_nbytes;
        __s64   aio_offset;

        /* extra parameters */
        __u64   aio_reserved2;  /* TODO: use this for a (struct sigevent *) */

        /* flags for the "struct iocb" */
        __u32   aio_flags;

        /*
         * if the IOCB_FLAG_RESFD flag of "aio_flags" is set, this is an
         * eventfd to signal AIO readiness to
         */
        __u32   aio_resfd;

*** SYNC RELATED COMMANDS ***
IOCB_CMD_FSYNC
   sync file's data and metadata with disk; corresponds to fsync() system call.

IOCB_CMD_FDSYNC
   sync file's data and metadata with disk, but only metadata needed to access
   modified file data is written; corresponds to fdatasync() system call.


*** VECTORED INPUT and OUTPUT ***
IOCB_CMD_PREADV
   vectored positioned read, sometimes called "scattered input";
   corresponds to pread() system call.

IOCB_CMD_PWRITEV
   vectored positioned write, sometimes called "gathered output";
   corresponds to pwrite() system call.

*** OTHER COMMANDS ***
IOCB_CMD_NOOP
   defined in the header file, but is not used anywhere else in the kernel.

XXX: May be discass Poll and other semi-existing commands here?...

*********************************************************
********************* LIBAIO LIBRARY ********************
*********************************************************

libaio:
/lib64/libaio.so.1 (shared library)

libaio-devel:
/usr/include/libaio.h (header library)
/usr/lib64/libaio.a (static library)

Functions:

a) Actual system call wrappers:

int io_setup(int maxevents, io_context_t *ctxp);
int io_destroy(io_context_t ctx);
int io_submit(io_context_t ctx, long nr, struct iocb *ios[]);
int io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt);
io_getevents(io_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);

io_context_t is a pointer to an non-existing stucture:

typedef struct io_context *io_context_t;

Not a single line of code in any user tool or in the libaio library looks at the
members of 'struct io_context'. So, gcc happily compiles the code even though
struct io_context is not defined.  This structure is probably defined just for
type checking. The rule of thumb when using libaio is just to declare all
variables as io_context_t and forget that it actually is a pointer!

b) Convenient macroses:

static inline void io_prep_pread(struct iocb *iocb, int fd, void *buf, size_t count, long long offset)
static inline void io_prep_pwrite(struct iocb *iocb, int fd, void *buf, size_t count, long long offset)
static inline void io_prep_preadv(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset)
static inline void io_prep_pwritev(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset)

static inline void io_prep_poll(struct iocb *iocb, int fd, int events)
static inline void io_prep_fsync(struct iocb *iocb, int fd)
static inline void io_prep_fdsync(struct iocb *iocb, int fd)

static inline int io_poll(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd, int events)
static inline int io_fsync(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd)
static inline int io_fdsync(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd)

static inline void io_set_eventfd(struct iocb *iocb, int eventfd);

*********************************************************
******** MATCHING LIBAIO AND KERNEL INTERFACE ***********
*********************************************************

libaio.h redefines some of the kernel definitions (god know why),
but they match at the binary level. E.g., this is kernel
exported definition of iocb:

struct iocb {
        /* these are internal to the kernel/libc. */
        __u64   aio_data;       /* data to be returned in event's data */
        __u32   PADDED(aio_key, aio_reserved1);
                                /* the kernel sets aio_key to the req # */

        /* common fields */
        __u16   aio_lio_opcode; /* see IOCB_CMD_ above */
        __s16   aio_reqprio;
        __u32   aio_fildes;

        __u64   aio_buf;
        __u64   aio_nbytes;
        __s64   aio_offset;

        /* extra parameters */
        __u64   aio_reserved2;  /* TODO: use this for a (struct sigevent *) */

        /* flags for the "struct iocb" */
        __u32   aio_flags;

        /*
         * if the IOCB_FLAG_RESFD flag of "aio_flags" is set, this is an
         * eventfd to signal AIO readiness to
         */
        __u32   aio_resfd;
}; /* 64 bytes */

And this is definition of iocb by libaio.h:

struct io_iocb_common {
        PADDEDptr(void  *buf, __pad1);
        PADDEDul(nbytes, __pad2);
        long long       offset;
        long long       __pad3;
        unsigned        flags;
        unsigned        resfd;
};      /* result code is the amount read or -'ve errno */


struct iocb {
        PADDEDptr(void *data, __pad1);  /* Return in the io completion event */
        PADDED(unsigned key, __pad2);   /* For use in identifying io requests */

        short           aio_lio_opcode;
        short           aio_reqprio;
        int             aio_fildes;

        union {
                struct io_iocb_common           c;
                struct io_iocb_vector           v;
                struct io_iocb_poll             poll;
                struct io_iocb_sockaddr saddr;
        } u;
};




****** AIO LIBRARY *****

glibc:
/lib64/librt.so.1

glibc-headers:
/usr/include/aio.h

Provide POSIX-defined interface for async I/O. 

aio_read()
aio_write()
aio_cancel()
aio_error()
aio_fsync()
aio_suspend()
aio_return()

lio_listio


****** To discover ****
XXX: see if these are implemented in some other kernels:
/* These two are experimental.
 * IOCB_CMD_PREADX = 4,
 * IOCB_CMD_POLL = 5,
 */
XXX: potential resubmittion of the wrong iocb, knowing its index.
XXX: two AIO contextes per process?
原文鏈接:https://www.fsl.cs.sunysb.edu/~vass/linux-aio.txt

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

-Advertisement-
Play Games
更多相關文章
  • 我們在使用 man 手冊的時候,可以使用man -f [keyword]去查詢keyword的線上文檔,但是這時候會報錯:(圖來源自網路) 這是因為我們還沒有建立 man 手冊的索引緩存; 我們可以使用 mandb 進行更新;之後就能使用 man -f 或者 man -k 命令進行查找手冊; 資源來 ...
  • Windows Update Delivery Optimization可幫助您更快,更可靠地獲取Windows更新和Microsoft Store應用程式。Windows Update Delivery Optimization的工作原理是,您可以從Microsoft以外的來源獲取Windows更... ...
  • 在Linux的文件系統中, 相應的文件都按其作用分門別類地放在相關的目錄中 以下是最近整理學習的一些, linux中的文件存放 /bin 二進位可執行命令 /dev 設備特殊文件 // 外部設備文件存在這裡 /etc 系統管理和配置文件 /etc/rc.d 啟動的配置文件和腳本 /home 用戶主目 ...
  • 一、iSCSI 服務部署網路存儲 伺服器配置 添加硬碟,創建分區 l[root@localhost Desktop]# ls /dev/sd* (系統下查看硬碟信息) /dev/sda /dev/sda1 /dev/sda2 /dev/sdb /dev/sdc /dev/sdd /dev/sde [ ...
  • 註意:配置bond要有兩個以上的網口 1、配置文件所有目錄:/etc/sysconfig/network-scripts 網口配置文件名規則:以ifcfg-開頭,然後接著是網口名 例如:eth0的配置文件名是 ifcfg-eth0 如果沒有配置文件,則需要創建網口配置文件(需要eth0和eth1) ...
  • 在手機中,我們常常使用備忘錄或者是鬧鐘等來提醒我們該做什麼事情了,在Linux操作系統中,也有類似的操作。 在Linux中除了用戶即時執行的命令操作以外,還可以配置在指定的時間、指定的日期執行預先計劃好的系統管理任務(如定期備份、定期採集監測數據)。RHEL6系統中預設已安裝了at、cronie軟體 ...
  • 服務名:sshd 客戶端配置文件:/etc/ssh/ssh_config 服務端配置文件:/etc/ssh/sshd_config sshd服務需要重啟才會生效 service sshd restart 第一步: 在客戶機生成公私鑰 # ssh-keygen 生成用戶的(註意和/etc/ssh/目錄 ...
  • cd 作用:切換路徑 切換至家目錄 在上一個目錄和當前目錄來回切換 切換至某用戶的家目錄 切換至上一級目錄 相對目錄實例 ...
一周排行
    -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 ...