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

C# 使用 IOCP 完成端口實作執行緒間資料傳遞(二)

     編輯:關於C語言

前言

本文為系列第二篇,深入剖析 GetQueuedCompletionStatus 參數意義、GCHandle 運作機制,並提供完整的執行緒池與完成端口互動範例程式碼。

GetQueuedCompletionStatus 參數詳解

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetQueuedCompletionStatus(
    SafeFileHandle CompletionPort,
    out uint lpNumberOfBytesTransferred,
    out IntPtr lpCompletionKey,
    out IntPtr lpOverlapped,
    uint dwMilliseconds);
  • CompletionPort:等待的完成端口控制代碼。
  • lpNumberOfBytesTransferred:實際傳輸位元組數。對於 PostQueuedCompletionStatus 發送的自訂訊息,此值為 dwNumberOfBytesTransferred 參數值。
  • lpCompletionKey:每個檔案/通訊端關聯的單一控制代碼資料(如 Socket 句柄)。本範例未使用。
  • lpOverlapped:每次 I/O 操作的上下文資料指標,亦可用於傳遞自訂物件(透過 GCHandle)。
  • dwMilliseconds:等待逾時,0xFFFFFFFF 表無限等待。

GCHandle 完整生命週期管理

  1. Alloc(object, GCHandleType):將物件加入 GC 控制代碼表,回傳 GCHandle 結構。Normal 允許 GC 移動物件,Pinned 固定記憶體位址。
  2. Target:取得/設定被追蹤的物件。
  3. Free():從 GC 控制代碼表移除,**必須在 Unmanaged 代碼不再使用後呼叫**,否則造成記憶體洩漏。

完整可執行範例

using System;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32.SafeHandles;

[StructLayout(LayoutKind.Sequential)]
class PerIoData { public string Data; }

public class IOCPApiTest
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern SafeFileHandle CreateIoCompletionPort(IntPtr FileHandle, IntPtr ExistingCompletionPort, IntPtr CompletionKey, uint NumberOfConcurrentThreads);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool GetQueuedCompletionStatus(SafeFileHandle CompletionPort, out uint lpNumberOfBytesTransferred, out IntPtr lpCompletionKey, out IntPtr lpOverlapped, uint dwMilliseconds);

    [DllImport("Kernel32", CharSet = CharSet.Auto)]
    private static extern bool PostQueuedCompletionStatus(SafeFileHandle CompletionPort, uint dwNumberOfBytesTransferred, IntPtr dwCompletionKey, IntPtr lpOverlapped);

    public static unsafe void TestIOCPApi()
    {
        var port = CreateIoCompletionPort(new IntPtr(-1), IntPtr.Zero, IntPtr.Zero, 1);
        if (port.IsInvalid)
        {
            Console.WriteLine($"建立失敗:{Marshal.GetLastWin32Error()}");
            return;
        }

        var worker = new Thread(ThreadProc);
        worker.Start(port);

        // 發送資料 1
        var d1 = new PerIoData { Data = "Hello from main thread" };
        var h1 = GCHandle.Alloc(d1);
        PostQueuedCompletionStatus(port, (uint)sizeof(IntPtr), IntPtr.Zero, (IntPtr)h1);

        // 發送資料 2(結束訊號)
        var d2 = new PerIoData { Data = "SHUTDOWN" };
        var h2 = GCHandle.Alloc(d2);
        PostQueuedCompletionStatus(port, 4, IntPtr.Zero, (IntPtr)h2);

        Console.WriteLine("主執行緒發送完畢");
        Console.ReadKey();
    }

    static void ThreadProc(object portObj)
    {
        var port = (SafeFileHandle)portObj;
        while (true)
        {
            uint bytes; IntPtr key, overlapped;
            Console.WriteLine($"[Worker {Thread.CurrentThread.ManagedThreadId}] 等待中...");
            GetQueuedCompletionStatus(port, out bytes, out key, out overlapped, 0xFFFFFFFF);

            if (bytes == 0) continue;

            var gch = GCHandle.FromIntPtr(overlapped);
            var data = (PerIoData)gch.Target;
            Console.WriteLine($"[Worker] 收到:{data.Data}");
            gch.Free();

            if (data.Data == "SHUTDOWN")
            {
                Console.WriteLine("[Worker] 關閉中...");
                port.Dispose();
                break;
            }
        }
    }

    public static int Main() { TestIOCPApi(); return 0; }
}

編譯與執行注意事項

  • 專案屬性 → Build → 勾選 Allow unsafe code。
  • 目標平台建議 x64,避免 IntPtr 大小不符。
  • 若在 Console 應用程式測試,確保主執行緒不會過早結束(用 Console.ReadKey() 等待)。

結語

掌握 GCHandle 與完成端口的交互細節,是建立高效能非同步 I/O 系統的基石。下一篇將介紹如何結合 Socket 實作真實的網路伺服器。

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