2022年3月24日 星期四

行程 (process) 和線程 (thread)

 

多行程 (Multi-Process) 還是多線程 (Multithreading)

作業系統在切換行程 (process) 或線程 (thread) 時需要先保存當前執行的環境,把新任務的執行環境準備好,才能開始執行。切換過程雖然很快,但是也需要耗費時間。任務一旦多到一個限度,反而使得系統性能下降,導致所有任務都做不好。

第二個考慮是任務的類型,可以把任務分為計算密集型和 I/O 密集型。
計算密集型任務的特點是要進行大量的計算,消耗 CPU 資源,雖然也可以用多任務完成,但是任務越多,花在任務切換的時間就越多,CPU 執行任務的效率就越低。這類任務用 Python 執行效率通常很低,最能勝任這類任務的是 C 語言,而 Python 中有嵌入 C/C++ 程式碼的機制。

其他涉及到網絡、存儲介質 I/O 的任務都可以視為 I/O 密集型任務,這類任務的特點是 CPU 消耗很少,任務的大部分時間都在等待 I /O 操作完成, I/O 的速度遠遠低於 CPU 和內存的速度。對於 I/O 密集型任務,如果啟動多任務,就可以減少 I/O 等待時間從而讓 CPU 高效率的運轉。

單線程 (thread) + 異步 I/O

可以利用作業系統提供的異步 I/O 支持,就能用單行程 (process) 單線程 (thread) 來執行多任務,這種模型稱為事件驅動模型。Nginx 就是支持異步 I/O 的 Web 服務器,在單核 CPU 上採用單行程 (process) 模型就可以高效地支持多任務。在多核 CPU 上,可以運行多個行程 (process),充分利用多核 CPU。用 Node.js 開發的服務器端程序也使用了這種工作模式。

在 Python 語言中,單線程 (thread) + 異步 I/O 的編程模型稱為協程,可以基於事件驅動編寫高效的多任務程序。
協程最大的優勢就是極高的執行效率,因為子程序切換不是線程 (thread) 切換,而是由程序自身控制。第二個優勢是不需要多線程 (Multithreading) 的鎖機制,因為只有一個線程 (thread),不存在同時寫變量衝突,只需要判斷狀態就好,所以執行效率高很多。如果想充分利用 CPU 的多核特性,最簡單的方法是多行程 (Multi-Process) + 協程。

範例

範例1 - 將耗時間的任務放到線程 (thread) 中以獲得更好的用戶體驗。
有'下載'和'關於'兩個按鈕,用休眠的方式模擬點擊'下載'按鈕會連網下載文件需要耗費 10 秒,如果不使用多線程 (Multithreading),當點擊'下載'按鈕後整個程序的其他部分都被這個耗時間的任務阻塞而無法執行。

import time
import tkinter
import tkinter.messagebox

def download():
    # 模擬下載任務需要花費 10 秒
    time.sleep(10)
    tkinter.messagebox.showinfo('提示', '下載完成!')

def show_about():
    tkinter.messagebox.showinfo('關於', 'welcome')

def main():
    top = tkinter.Tk()
    top.title('單線程')          # 視窗標題
    top.geometry('250x150')      # 視窗大小
    top.wm_attributes('-topmost', True)  # 視窗彈出置頂的

    panel = tkinter.Frame(top)  
    button1 = tkinter.Button (panel, text = '下載', command = download)
    button1.pack(side = 'left')
    button2 = tkinter.Button (panel, text = '關於', command = show_about)
    button2.pack(side = 'right')
    panel.pack(side = 'bottom')

    tkinter.mainloop()

if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20190927/20121116AIWcD5tX6u.png
如果使用多線程 (Multithreading) 將耗時間的任務放到一個獨立的線程 (thread) 中執行,這樣就不會因為執行耗時間的任務而阻塞了主線程 (thread) 。

import time
import tkinter
import tkinter.messagebox
from threading import Thread

def main():

    class DownloadTaskHandler(Thread):

        def run(self):
            time.sleep(10)
            tkinter.messagebox.showinfo('提示', '下載完成!')
            # 啟用下載按鈕
            button1.config(state = tkinter.NORMAL)

    def download():
        # 禁用下載按鈕
        button1.config(state = tkinter.DISABLED)
        # 透過 daemon 參數將線程 (thread) 設為守護線程 (thread), 
        # 主程式退出就不再保留執行
        # 在線程中處理耗時間的下載任務
        DownloadTaskHandler(daemon = True).start()

    def show_about():
        tkinter.messagebox.showinfo('關於', 'welcome')

    top = tkinter.Tk()
    top.title('單線程')          # 視窗標題
    top.geometry('250x150')      # 視窗大小
    top.wm_attributes('-topmost', 1)  # 視窗彈出置頂的

    panel = tkinter.Frame(top)
    button1 = tkinter.Button (panel, text = '下載', command = download)
    button1.pack(side = 'left')
    button2 = tkinter.Button(panel, text = '關於', command = show_about)
    button2.pack(side = 'right')
    panel.pack(side = 'bottom')

    tkinter.mainloop()


if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20190927/20121116buScz4eAdk.png
圖中可以看出按鈕被進用了。
範例2 - 使用多行程 (Multi-Process)對複雜任務進行'分而治之'。
完成 1 ~ 100000000 求和的計算密集型任務。

from time import time

def main():
    total = 0
    number_list = [x for x in range(1, 100000001)]
    start = time()
    for number in number_list:
        total += number
    print(total)
    end = time()
    print('Execution time: %.3fs' % (end - start))

if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20190927/20121116sE9JtNpBRd.png

from multiprocessing import Process, Queue
from random import randint
from time import time

def task_handler (curr_list, result_queue):
    total = 0
    for number in curr_list:
        total += number
    result_queue.put(total)

def main():
    processes = []
    number_list = [x for x in range(1, 100000001)]
    result_queue = Queue()
    index = 0
    # 啟動 8 個行程 (process) 將數據切片後進行運算
    for _ in range(8):
        p = Process (target = task_handler,
                    args = (number_list[index:index + 12500000], result_queue))
        index += 12500000
        processes.append(p)
        p.start()
    # 開始記錄所有行程 (process) 執行完成花費的時間
    start = time()
    for p in processes:
        p.join()
    # 執行结果
    total = 0
    while not result_queue.empty():
        total += result_queue.get()
    print(total)
    end = time()
    print('Execution time: ', (end - start), 's', sep = '')

if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20190927/201211169tbMQlLMly.png
比較兩段程式碼的執行結果,使用多行程 (Multi-Process) 後由於獲得了更多的 CPU 執行時間以及更好的利用了 CPU 的多核特性,明顯的減少程式的執行時間,而且計算量越大效果越明顯。

資料來源:https://ithelp.ithome.com.tw/articles/10221364

如何利用多核CPU來加速你的Linux命令

  你是否曾經有過要計算一個非常大的資料(幾百GB)的需求?或在裡面搜尋,或其它操作——一些無法並行的操作。資料專家們,我是在對你們說。你可能有一個4核或更多核的CPU,但我們合適的工具,例如 grep, bzip2, wc, awk, sed等等,都是單執行緒的,只能使用一個CPU核心。

  借用卡通人物Cartman的話,“如何我能使用這些核心”?

  要想讓Linux命令使用所有的CPU核心,我們需要用到GNU Parallel命令,它讓我們所有的CPU核心在單機內做神奇的map-reduce操作,當然,這還要藉助很少用到的–pipes 引數(也叫做–spreadstdin)。這樣,你的負載就會平均分配到各CPU上,真的。

  BZIP2

  bzip2是比gzip更好的壓縮工具,但它很慢!別折騰了,我們有辦法解決這問題。

  以前的做法:

cat bigfile.bin | bzip2 --best > compressedfile.bz2

  現在這樣:

cat bigfile.bin | parallel --pipe --recend '' -k bzip2 --best > compressedfile.bz2

  尤其是針對bzip2,GNU parallel在多核CPU上是超級的快。你一不留神,它就執行完成了。

  GREP

  如果你有一個非常大的文字檔案,以前你可能會這樣:

grep pattern bigfile.txt

 現在你可以這樣:

cat bigfile.txt | parallel  --pipe grep 'pattern'

  或者這樣:

cat bigfile.txt | parallel --block 10M --pipe grep 'pattern'

  這第二種用法使用了 –block 10M引數,這是說每個核心處理1千萬行——你可以用這個引數來調整每個CUP核心處理多少行資料。

  AWK

  下面是一個用awk命令計算一個非常大的資料檔案的例子。

  常規用法:

cat rands20M.txt | awk '{s+=$1} END {print s}'

  現在這樣:

cat rands20M.txt | parallel --pipe awk \'{s+=\$1} END {print s}\' | awk '{s+=$1} END {print s}'

  這個有點複雜:parallel命令中的–pipe引數將cat輸出分成多個塊分派給awk呼叫,形成了很多子計算操作。這些子計算經過第二個管道進入了同一個awk命令,從而輸出最終結果。第一個awk有三個反斜槓,這是GNU parallel呼叫awk的需要。

  WC

  想要最快的速度計算一個檔案的行數嗎?

  傳統做法:

wc -l bigfile.txt

  現在你應該這樣:

cat bigfile.txt | parallel  --pipe wc -l | awk '{s+=$1} END {print s}'

  非常的巧妙,先使用parallel命令‘mapping’出大量的wc -l呼叫,形成子計算,最後通過管道傳送給awk進行彙總。

  SED

  想在一個巨大的檔案裡使用sed命令做大量的替換操作嗎?

  常規做法:

sed s^old^new^g bigfile.txt

  現在你可以:

cat bigfile.txt | parallel --pipe sed s^old^new^g

  …然後你可以使用管道把輸出儲存到指定的檔案裡。

  英文原文:Use multiple CPU Cores with your Linux commands 

資料來源:https://iter01.com/34786.html

2022年3月21日 星期一

讀 4pin 風扇 RPM

 


uint8_t GPIO_Pin = D2;

uint8_t PWM_Pin = D6;

unsigned long duration;

uint8_t getData;


unsigned int rpm;

unsigned long timeold;


void setup() {

  Serial.begin(115200);

  attachInterrupt(digitalPinToInterrupt(GPIO_Pin), IntCallback, RISING);

  pinMode(GPIO_Pin, INPUT_PULLUP);

  pinMode(PWM_Pin, OUTPUT);

  analogWrite(PWM_Pin, 0);


  duration = 0;

  rpm = 0;

  timeold = 0;

}


void loop() {


  if (Serial.available()) {      // If anything comes in Serial (USB),

    getData = Serial.read();   // read it and send it out Serial1 (pins 0 & 1)

    if( getData >='0' && getData <=':' ){

      //Serial.println(getData);

      analogWrite(PWM_Pin, map((getData-'0')*10, 0, 100, 0, 255) );

      Serial.print("analogWrite ");

      Serial.println((getData-'0')*10);

    }

  }

  

  if (duration >= 2) {

    rpm = 30 *1000 / (millis() - timeold) * duration;

    //Serial.println(duration,DEC);

    //Serial.println((millis() - timeold),DEC);

    Serial.print("目前轉速:");

    Serial.println(rpm,DEC);

    timeold = millis();

    duration = 0;

  }


}


ICACHE_RAM_ATTR void IntCallback(){

  duration++;

}




2022年3月18日 星期五

C# .Net 多程序同步 通訊 共享記憶體 記憶體對映檔案 Memory Mapped

節點通訊存在兩種模型:共享記憶體(Shared memory)和訊息傳遞(Messages passing)。

        記憶體對映檔案對於託管世界的開發人員來說似乎很陌生,但它確實已經是很遠古的技術了,而且在作業系統中地位相當。實際上,任何想要共享資料的通訊模型都會在幕後使用它。

        記憶體對映檔案究竟是個什麼?記憶體對映檔案允許你保留一塊地址空間,然後將該物理儲存對映到這塊記憶體空間中進行操作。物理儲存是檔案管理,而記憶體對映檔案是作業系統級記憶體管理

優勢:

     1.訪問磁碟檔案上的資料不需執行I/O操作和快取操作(當訪問檔案資料時,作用尤其顯著);
     2.讓執行在同一臺機器上的多個程序共享資料(單機多程序間資料通訊效率最高);

       利用檔案與記憶體空間之間的對映,應用程式(包括多個程序)可以通過直接在記憶體中進行讀寫來修改檔案。.NET Framework 4 用託管程式碼按照本機Windows函式訪問記憶體對映檔案的方式來訪問記憶體對映檔案,管理 Win32 中的記憶體對映檔案 。

有兩種型別的記憶體對映檔案:

  • 持久記憶體對映檔案

    持久檔案是與磁碟上的原始檔關聯的記憶體對映檔案。在最後一個程序使用完此檔案後,資料將儲存到磁碟上的原始檔中。這些記憶體對映檔案適合用來處理非常大的原始檔。

  • 非持久記憶體對映檔案

    非持久檔案是未與磁碟上的原始檔關聯的記憶體對映檔案。當最後一個程序使用完此檔案後,資料將丟失,並且垃圾回收功能將回收此檔案。這些檔案適用於為程序間通訊 (IPC) 建立共享記憶體。

    1)在多個程序之間進行共享(程序可通過使用由建立同一記憶體對映檔案的程序所指派的公用名來對映到此檔案)。

    2)若要使用一個記憶體對映檔案,則必須建立該記憶體對映檔案的完整檢視或部分檢視。還可以建立記憶體對映檔案的同一部分的多個檢視,進而建立併發記憶體為了使兩個檢視能夠併發,必須基於同一記憶體對映檔案建立這兩個檢視

    3)如果檔案大於應用程式用於記憶體對映的邏輯記憶體空間(在 32 位計算機上為2GB),則還需要使用多個檢視。

有兩種型別的檢視:流訪問檢視和隨機訪問檢視。使用流訪問檢視可對檔案進行順序訪問;在使用持久檔案時,隨機訪問檢視是首選方法。

    .Net 共享記憶體 記憶體對映檔案原理:通過作業系統的記憶體管理器訪問的,因此會自動將此檔案分隔為多個頁,並根據需要對其進行訪問。您不需要自行處理記憶體管理。

C# .Net 共享記憶體 演示程式碼如下:

    //持久記憶體對映檔案:基於現有檔案建立一個具有指定公用名的記憶體對映檔案

    using (var mmf = MemoryMappedFile.CreateFromFile(@"c:\記憶體對映檔案.data", FileMode.Open, "公用名"))
    {
        //通過指定的 偏移量和大小 建立記憶體對映檔案檢視伺服器
        using (var accessor = mmf.CreateViewAccessor(offset, length)) //偏移量,可以控制資料儲存的記憶體位置;大小,用來控制儲存所佔用的空間
        {
            //Marshal提供了一個方法集,這些方法用於分配非託管記憶體、複製非託管記憶體塊、將託管型別轉換為非託管型別,此外還提供了在與非託管程式碼互動時使用的其他雜項方法。

            int size = Marshal.SizeOf(typeof(char));

            //修改記憶體對映檔案檢視
            for (long i = 0; i < length; i += size)
            {
                char c= accessor.ReadChar(i);
                accessor.Write(i, ref c);
            }
        }
    }

    //另一個程序或執行緒可以,在系統記憶體中開啟一個具有指定名稱的現有記憶體對映檔案

    using (var mmf = MemoryMappedFile.OpenExisting("公用名"))
    {
        using (var accessor = mmf.CreateViewAccessor(4000000, 2000000))
        {
            int size = Marshal.SizeOf(typeof(char));
            for (long i = 0; i < length; i += size)
            {
                char c = accessor.ReadChar(i);
                accessor.Write(i, ref c);
            }
        }
    }

    //非持久記憶體對映檔案:未對映到磁碟上的現有檔案的記憶體對映檔案

    using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("testmap", 10000))
    {
        bool mutexCreated;
        //程序間同步
        Mutex mutex = newMutex(true, "testmapmutex", out mutexCreated);
        using (var stream = mmf.CreateViewStream()) //建立檔案記憶體檢視流 基於流的操作
        {
            var writer = newBinaryWriter(stream);
            writer.Write(1);
        }
        mutex.ReleaseMutex();

        Console.WriteLine("Start Process B and press ENTER to continue.");
        Console.ReadLine();

        mutex.WaitOne();
        using (MemoryMappedViewStream stream = mmf.CreateViewStream())
        {
            var reader = newBinaryReader(stream);
            Console.WriteLine("Process A says: {0}", reader.ReadBoolean());
            Console.WriteLine("Process B says: {0}", reader.ReadBoolean());
        }
        mutex.ReleaseMutex();
    }

    using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("testmap"))
    {
         Mutex mutex = Mutex.OpenExisting("testmapmutex");
        mutex.WaitOne();
       using (var stream = mmf.CreateViewStream(1, 0))//注意這裡的偏移量
        {
            var writer = newBinaryWriter(stream);
            writer.Write(0);
        }
        mutex.ReleaseMutex();

    }

 C# .Net  程序間通訊 共享記憶體 完整示例: C#共享記憶體非持久化方式通訊的例子,通訊時的執行緒和程序控制也沒有問題。如下是實現的程式碼。

先啟動訊息服務IMServer_Message,

再啟動狀態服務IMServer_State,

IMServer_Message回車一次(建立共享記憶體公用名和公用執行緒鎖,並檢視流方式寫共享記憶體),

IMServer_State回車一次(獲取共享記憶體並檢視流方式寫、檢視訪問器寫入結構體型別)

並立刻IMServer_Message再回車一次(讀取剛剛寫入的資訊),

觀察IMServer_State屏顯變化並等待(執行緒鎖)約5s(執行緒鎖被釋放)後

在IMServer_Message上觀察屏顯(顯示剛剛寫入共享記憶體的資訊)

 IMServer_Message.exe 程式碼

using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Threading;

namespace IMServer_Message
{
    /// <summary>
    /// 用於共享記憶體方式通訊的 值型別 結構體
    /// </summary>
    public struct ServiceMsg
    {
        public int Id;
        public long NowTime;
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.Write("請輸入共享記憶體公用名(預設:testmap):");
            string shareName = Console.ReadLine();
            if (string.IsNullOrEmpty(shareName))
                shareName = "testmap";
            using (MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(shareName, 1024000,MemoryMappedFileAccess.ReadWrite))
            {
                bool mutexCreated;
                //程序間同步
                var mutex = new Mutex(true, "testmapmutex", out mutexCreated);
                using (MemoryMappedViewStream stream = mmf.CreateViewStream()) //建立檔案記憶體檢視流
                {
                    var writer = new BinaryWriter(stream);
                    for (int i = 0; i < 5; i++)
                    {
                        writer.Write(i);
                        Console.WriteLine("{0}位置寫入流:{0}", i);
                    }
                }

                mutex.ReleaseMutex();

                Console.WriteLine("啟動狀態服務,按【回車】讀取共享記憶體資料");
                Console.ReadLine();

                mutex.WaitOne();
                using (MemoryMappedViewStream stream = mmf.CreateViewStream())
                {
                    var reader = new BinaryReader(stream);
                    for (int i = 0; i < 10; i++)
                    {
                        Console.WriteLine("{1}位置:{0}", reader.ReadInt32(), i);
                    }
                }

                using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(1024, 10240))
                {
                    int colorSize = Marshal.SizeOf(typeof (ServiceMsg));
                    ServiceMsg color;
                    for (int i = 0; i < 50; i += colorSize)
                    {
                        accessor.Read(i, out color);
                        Console.WriteLine("{1}\tNowTime:{0}", new DateTime(color.NowTime), color.Id);
                    }
                }
                mutex.ReleaseMutex();
            }
            Console.WriteLine("測試: 我是 即時通訊 - 訊息服務 我啟動啦!!!");
            Console.ReadKey();
        }
    }
}

IMServer_State.exe程式碼

using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Threading;

namespace IMServer_State
{
    /// <summary>
    /// 用於共享記憶體方式通訊的 值型別 結構體
    /// </summary>
    public struct ServiceMsg
    {
        public int Id;
        public long NowTime;
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.Write("請輸入共享記憶體公用名(預設:testmap):");
            string shareName = Console.ReadLine();
            if (string.IsNullOrEmpty(shareName))
                shareName = "testmap";
            using (MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(shareName, 1024000,MemoryMappedFileAccess.ReadWrite))
            {
                Mutex mutex = Mutex.OpenExisting("testmapmutex");
                mutex.WaitOne();
                using (MemoryMappedViewStream stream = mmf.CreateViewStream(20, 0)) //注意這裡的偏移量
                {
                    var writer = new BinaryWriter(stream);
                    for (int i = 5; i < 10; i++)
                    {
                        writer.Write(i);
                        Console.WriteLine("{0}位置寫入流:{0}", i);
                    }
                }
                using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(1024, 10240))
                {
                    int colorSize = Marshal.SizeOf(typeof (ServiceMsg));
                    var color = new ServiceMsg();
                    for (int i = 0; i < colorSize*5; i += colorSize)
                    {
                        color.Id = i;
                        color.NowTime = DateTime.Now.Ticks;
                        //accessor.Read(i, out color);
                        accessor.Write(i, ref color);
                        Console.WriteLine("{1}\tNowTime:{0}", new DateTime(color.NowTime), color.Id);
                        Thread.Sleep(1000);
                    }
                }
                Thread.Sleep(5000);

                mutex.ReleaseMutex();
            }
            Console.WriteLine("測試: 我是 即時通訊 - 狀態服務 我啟動啦!!!");
            Console.ReadKey();
        }
    }

} 

資料來源:https://www.itread01.com/p/1419330.html

C# 實作載入外部 DLL 外並使用 Method

呼叫 DLL 裡的 Form , Function


namespace DLL_RS232

{

    public partial class cDLL_RS232 : Form

    {

        String[] TestItems = {

            "Test Item 1", "1000"

            , "Test Item 2", "2000"

            , "Test Item 3", "3000"

            , "Test Item 4", "4000"

        };

        public cDLL_RS232()

        {

            InitializeComponent();

            Console.WriteLine("cDLL_RS232");

        }

        public string[] GetItemsList()

        {

            Console.WriteLine("GetItemsList");

            return TestItems;

        }

        public int Add(int a, int b)

        {

            return a + b;

        }


        private void label1_Click(object sender, EventArgs e)

        {

            int value = Add(11, 22);

            label1.Text = value.ToString();

        }

    }

}

            string path = @".\DLL_RS232.dll";

            Assembly assembly = Assembly.LoadFrom(path);

            Type type;  //獲取程式集例項中具有指定名稱的Type物件

            type = assembly.GetType("DLL_RS232.cDLL_RS232");

            object obj = assembly.CreateInstance(type.FullName, true);

            ((Form)obj).ShowDialog();


            MethodInfo miGetMethod = type.GetMethod("GetItemsList");

            TestItems = (string[])miGetMethod.Invoke(obj, null);


            miGetMethod = type.GetMethod("Add");

            int strResult = (int)miGetMethod.Invoke(obj, new object[] { 11, 22 });



C# - 實作載入外部 DLL 外並使用 Method

摘要:C# - 實作載入外部 DLL 外並使用 Method

這陣子都在開發實作 WinForm,坦白說 Web 都快要忘光了,但也讓我見識到 WinForm 一些高深的功能。其中就是載入外部的 DLL 外並且使用 DLL 中的 Method,這技術在我師父及老手眼中,雖然是一項十年前就已經用過的東西。但小弟道行日淺,仍然是有一些新鮮感,以下就來實作唄...

步驟一:建立一個名為「TESTAP1」的 WinForm,並且設計一下畫面

步驟二:除了實作按鈕事件,外加實作兩個 Method,其中一個 Method 需要傳入參數

Code:

namespace TESTAP1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Hello Danny!!!");
        }

        public string GetHollePeggy()
        {
            return "Holle Peggy!!!";
        }

        public string GetDcLoveEP(string str_Param_1, string str_Param_2)
        {
            return string.Format("{0} LOVE {1}...^_^Y", str_Param_1, str_Param_2);
        }
    }
}

步驟三:點選專案並按右鍵,再點選屬性

步驟四:在應用程式頁籤中,將輸出類型從Windows應用程式改為類別庫,這樣才會讓此專案編譯後產生DLL,完成後請編譯專案

步驟五:再建立一個名為「TESTAP2」的 WinForm,並且設計一下畫面

步驟六:針對三個按鈕實作功能

Code:

namespace TESTAP2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        static Assembly assembly = Assembly.LoadFrom(@"C:\...省略...\TESTAP1.dll");

                private void button1_Click(object sender, EventArgs e)
        {
            Type type = assembly.GetType("TESTAP1.Form1");

            object obj = assembly.CreateInstance(type.FullName, true);

            ((Form)obj).ShowDialog();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Type type = assembly.GetType("TESTAP1.Form1");

            MethodInfo miGetMethod = type.GetMethod("GetHollePeggy");

            object obj = assembly.CreateInstance(type.FullName, true);

            string strResult = (string)miGetMethod.Invoke(obj, null);

            MessageBox.Show(strResult);
        }

        private void button3_Click(object sender, EventArgs e)
        {
            Type type = assembly.GetType("TESTAP1.Form1");

            MethodInfo miGetMethod = type.GetMethod("GetDcLoveEP");

            object obj = assembly.CreateInstance(type.FullName, true);

            string strResult = (string)miGetMethod.Invoke(obj, new string[] { "Danny", "Peggy" });

            MessageBox.Show(strResult);
        }
    }
}

結果:

1.開啟 DLL

2.開啟 DLL 中的 Method

3.開啟 DLL 中的 Method 避帶入參數

參考:

C#實現反射調用動態加載的DLL文件中的方法
C#動態創建類的實例
通過Assembly來動態加載DLL
C#調用託管非託管動態庫(動態調用)

資料來源:https://dotblogs.com.tw/dc690216/2010/08/01/16939

2022年3月17日 星期四

C# 委派 (Delegate) 函數指標

 Introduction

委派可以看成方法的指標,利用委派可以間接叫用方法。

委派通常主要應用於兩方面:

  1. 事件的驅動
  2. 兩個處理程序間互相呼叫(Call Back)。

 

Example

使用委派的三個步驟:

  1. 宣告委派型別。
  • [ public | protected | private ] delegate 回傳值型態 委派名稱 ( 參數群 )

    2.    實體化委派型別並指向相對應方法。

  • 建立委派物件實體時,必須要傳入符合委派規格的方法參考

    3.    使用 Invoke 方法叫用委派。

    另外  : 如果要將新的方法的位置參考加入到委派物件的執行方法清單的話,必須透過 「+=」 關鍵字。

sample1

委派的基本操作


class Program {

        //step1:宣告委派型別。
        public delegate void MyDelegate(string name);

        static void Main(string[] args) {
            //step2:實體化委派型別並指向相對應方法。
            //      MyDelegate 委派,為沒有傳回值,
            //      並且傳入參數為一個字串型別。
            MyDelegate oMyDel = new MyDelegate(Show);

            //.net 2.0 之後可以簡化。
            MyDelegate oYourDel = Show;

            //將方法加入委派物件的執行方法清單中
            oYourDel += new MyDelegate(Show2);

            //step3 : 使用 Invoke 方法叫用委派。
            oMyDel.Invoke("MyHello!");
            oYourDel.Invoke("YourHello!");

            //也可簡化。
            oMyDel("MyHello!");
            oYourDel("YourHello!");

          
            Console.ReadKey();
        }

        public static void Show(string value) {
            Console.WriteLine("Show : {0}",value);
        }

        public static void Show2(string value) {
            Console.WriteLine("Show2 : {0}",value);
        }
    }

結果

tmp

 

 

sample2

匿名方法 (Anonymous method) 是 .net 2.0 的新功能,當執行委派所指定的方法是一些名稱不太重要的

方法時,可以省略方法名稱。


///不使用匿名方法
public delegate MyShow(string m);

public void show(string value){
   MessageBox.show(value);	
}

private void Form1_Load(object sender,EventArgs e){
   MyShow oMyDel = show;
   oMyDel.Invoke("不使用匿名方法");
}

 


///使用匿名方法
public delegate void MyShow(string m)

private void Form1_Load(object sender,EventArts e){
     MyShow oMyDel = delegate(string m){
          MessageBox.Show(m); 
     };
     oMyDel.Invoke("使用匿名方法");
}

 

 

 

sample3

多重傳送委派  是單一事件引發多個事件,利用 「-」與「+」號完成委派的新增與刪除。

其中,以下兩個方法得到的結果會是一樣的。


//直接加方法
del d = method1;
d = d + method2;
d.Invoke();

//直接加委派
del d1 = method1;
del d2 = method2;
d1 += d2;
d1.Invoke();

 


public delegate void Del();

        private static void Show() {
            Console.WriteLine("第一個呼叫");
        }

        private static void Show2() {
            Console.WriteLine("第二個呼叫");
        }

        private static void Show3() {
            Console.WriteLine("第三個呼叫");
        }

        static void Main(string[] args) {
            Del oMyDel = Show;
            oMyDel = oMyDel + Show2;
            oMyDel += Show3;
            oMyDel.Invoke();

            Console.ReadKey();
        }

結果

tmp

 

sample4

實現 CallBack


//宣告委派型別
    public delegate string MyDel();

    class A {
        private string _name = "我是 A";

        //建立與委派型別對應的方法
        public string showInfo() {
            return this._name;
        }

        public A() {
            //建立委派驅動
            DelDriver dirver = new DelDriver();
            //建立委派實例並且指定方法
            MyDel d1 = showInfo;
            //呼叫驅動方法並且傳入委派物件
            dirver.delDirver(d1);
        }
    }

    class B {
        private string _name = "我是 B";

        //建立與委派型別對應的方法
        public string showInfo() {
            return this._name;
        }

        public B() {
            DelDriver dirver = new DelDriver();
            MyDel d1 = showInfo;
            dirver.delDirver(d1);
        }
    }

    class DelDriver {
        public void delDirver(MyDel del) {
            Console.WriteLine(del.Invoke());
        }
    }

    class Program {
        static void Main(string[] args) {
            A a = new A();
            B b = new B();

            Console.ReadKey();
        }
    }

結果

tmp

 

sample5

利用 Lambda 運算式 完成委派,這個方法僅適用 .net 3.0 之後的版本。

Lambda 運算式」(Lambda Expression) 是一種匿名函式,它可以包含運算式和陳述式 (Statement),而且可以用來建立委派 (Delegate) 或運算式樹狀架構型別。

所有的 Lambda 運算式都會使用 Lambda 運算子 =>,意思為「移至」。Lambda 運算子的左邊會指定輸入參數 (如果存在),右邊則包含運算式或陳述式區塊。


 //宣告委派型別
        public delegate void Del(string m);

        static void Main(string[] args) {            
            //建立委派物件
            //大括號裡面為匿名方法,左方 s 變數對應匿名方法,傳入。
            Del oMyDel = (string s) => { Console.WriteLine(s); };
            oMyDel("Hello!");
            Console.ReadKey();
        }

結果

三小俠

 

 

Link

委派 (C# 程式設計手冊)

HOW TO:宣告、產生和使用委派 (C# 程式設計手冊)

使用具名和匿名方法委派的比較 (C# 程式設計手冊)

使用委派取代介面的時機 (C# 程式設計手冊)

委派中的 Covariance 和 Contravariance (C# 程式設計手冊)

HOW TO:組合委派 (多點傳送委派) (C# 程式設計手冊)

Lambda 運算式 (C# 程式設計手冊)

匿名方法 (C# 程式設計手冊)

 

<<後記>>

1. 使用委派建立執行緒參數

    class Program {
        static void Main(string[] args) {

            StartThread();
            StartThread2();
            Console.ReadKey();
        }

        static void StartThread() {
            //使用匿名方法,建立委派不具參數的委派
            System.Threading.Thread t1 = new System.Threading.Thread
             (delegate() {                
                System.Console.WriteLine("World!");
            });
            t1.Start();
            
        }

        static void StartThread2() {
            //使用匿名方法,建立帶有參數的委派
            System.Threading.Thread t1 = new System.Threading.Thread
              (delegate(object value) {
                System.Console.Write(value);
                    });
            t1.Start("Hello World!");
        }

    }

 

<<後記2>>

在網路上不小心發現 蔡學鏞 大師有介紹 C# 的函數指標,大家可以參考一下

函數指標的進化論 (上)
函數指標的進化論 (下)

資料來源:https://dotblogs.com.tw/atowngit/2009/12/07/12311