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

多執行緒 DAO 程式設計:GUI 執行緒封送與 CMultiDaoRecordSet 實作模式

     編輯:關於C語言

DAO(Data Access Objects)在多執行緒環境下有嚴格限制:DAO 物件必須在建立它的執行緒中使用,跨執行緒直接呼叫會導致未定義行為或崩潰。本文提出一種以 GUI 執行緒封送 為核心的解決方案,封裝 CMultiDaoRecordSet 基類,透過視窗訊息將背景執行緒的 DAO 操作請求轉發至主執行緒執行,確保執行緒親和性並簡化上層邏輯。

DAO 執行緒親和性限制

  • DAO 內部使用單執行緒 COM 單元(STA),物件綁定建立時的執行緒。
  • 背景執行緒直接操作 CDaoRecordset 會觸發 DAO 3609 錯誤或存取違規。
  • MFC CDaoDatabase/CDaoRecordset 無內建跨執行緒封送機制。

核心架構:訊息驅動封送模式

sequenceDiagram
    participant Worker as 背景執行緒
    participant Main as GUI 主執行緒
    participant DAO as CDaoRecordset

    Worker->>Main: PostMessage(WM_MULTIDAOMESSAGE, cmd, pRecordset)
    Main->>Main: OnMultiDaoMessage() 接收
    Main->>DAO: 執行實際 DAO 操作 (Open/Edit/Update/Delete)
    DAO-->>Main: 回傳結果/錯誤碼
    Main->>Worker: PostThreadMessage / 事件通知完成

關鍵類別實作

1. 自訂訊息定義

// MultiDaoRecordset.h
#define WM_MULTIDAOMESSAGE RegisterWindowMessage(_T("WM_MULTIDAOMESSAGE"))

enum MultiDaoCmd {
    DAO_CMD_OPEN,
    DAO_CMD_CLOSE,
    DAO_CMD_MOVE_NEXT,
    DAO_CMD_MOVE_PREV,
    DAO_CMD_EDIT,
    DAO_CMD_UPDATE,
    DAO_CMD_DELETE,
    DAO_CMD_ADDNEW,
    DAO_CMD_FIND,
    DAO_CMD_REQUERY
};

struct MultiDaoParam {
    MultiDaoCmd cmd;
    CMultiDaoRecordSet* pSet;
    COleVariant var1, var2; // 通用參數
    LONG lResult;           // 執行結果
    CString strError;       // 錯誤訊息
    HANDLE hEvent;          // 同步事件(可選)
};

2. 基類 CMultiDaoRecordSet

class CMultiDaoRecordSet : public CDaoRecordset
{
    DECLARE_DYNAMIC(CMultiDaoRecordSet)

public:
    CMultiDaoRecordSet(CDaoDatabase* pdb = NULL) : CDaoRecordset(pdb) {}

    // 覆寫關鍵虛擬函式,攔截呼叫並轉發至 GUI 執行緒
    virtual BOOL Open(int nOpenType = dbOpenDynaset, LPCTSTR lpszSQL = NULL, int nOptions = 0) {
        return MarshalCall(DAO_CMD_OPEN, (LPARAM)lpszSQL, nOptions);
    }
    virtual void Close() { MarshalCall(DAO_CMD_CLOSE); }
    virtual BOOL MoveNext() { return (BOOL)MarshalCall(DAO_CMD_MOVE_NEXT); }
    virtual BOOL MovePrev() { return (BOOL)MarshalCall(DAO_CMD_MOVE_PREV); }
    virtual void Edit() { MarshalCall(DAO_CMD_EDIT); }
    virtual void Update() { MarshalCall(DAO_CMD_UPDATE); }
    virtual void Delete() { MarshalCall(DAO_CMD_DELETE); }
    virtual void AddNew() { MarshalCall(DAO_CMD_ADDNEW); }

protected:
    LONG MarshalCall(MultiDaoCmd cmd, LPARAM param1 = 0, LPARAM param2 = 0) {
        // 檢查是否已在 GUI 執行緒
        if (AfxGetThread() == AfxGetApp()->m_pMainWnd->GetWindowThreadProcessId()) {
            // 直接執行基類實作
            return DirectCall(cmd, param1, param2);
        }

        // 封裝參數
        MultiDaoParam params;
        params.cmd = cmd;
        params.pSet = this;
        params.var1 = param1; params.var2 = param2;
        params.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

        // 發送至主視窗
        ::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), WM_MULTIDAOMESSAGE, 0, (LPARAM)¶ms);

        // 等待完成(可選:非同步模式則不等待,改用回調)
        WaitForSingleObject(params.hEvent, INFINITE);
        CloseHandle(params.hEvent);

        if (!params.strError.IsEmpty())
            AfxThrowDaoException(params.lResult, params.strError);
        return params.lResult;
    }

    LONG DirectCall(MultiDaoCmd cmd, LPARAM p1, LPARAM p2) {
        switch (cmd) {
            case DAO_CMD_OPEN:  return CDaoRecordset::Open((int)p1, (LPCTSTR)p2);
            case DAO_CMD_CLOSE: CDaoRecordset::Close(); return 0;
            case DAO_CMD_MOVE_NEXT: return CDaoRecordset::MoveNext();
            // ... 其他命令
        }
        return 0;
    }
};
IMPLEMENT_DYNAMIC(CMultiDaoRecordSet, CDaoRecordset)

3. 主視窗訊息處理

// MainFrm.h
#define MAINFRAME_CPP
#include "MultiDaoRecordset.h"
UINT WM_MULTIDAOMESSAGE = RegisterWindowMessage(_T("WM_MULTIDAOMESSAGE"));

class CMainFrame : public CMDIFrameWnd {
    // ...
    afx_msg LONG OnMultiDaoMessage(UINT uParam, LONG lParam);
};

// MainFrm.cpp
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
    ON_REGISTERED_MESSAGE(WM_MULTIDAOMESSAGE, OnMultiDaoMessage)
END_MESSAGE_MAP()

LONG CMainFrame::OnMultiDaoMessage(UINT uParam, LONG lParam)
{
    MultiDaoParam* pParams = (MultiDaoParam*)lParam;
    CMultiDaoRecordSet* pSet = pParams->pSet;
    LONG lRet = 0;
    CString strErr;

    try {
        switch (pParams->cmd) {
            case DAO_CMD_OPEN:
                lRet = pSet->CDaoRecordset::Open((int)pParams->var1.lVal,
                                                 (LPCTSTR)pParams->var1.pbstrVal,
                                                 (int)pParams->var2.lVal);
                break;
            case DAO_CMD_CLOSE:
                pSet->CDaoRecordset::Close(); break;
            case DAO_CMD_MOVE_NEXT:
                lRet = pSet->CDaoRecordset::MoveNext(); break;
            case DAO_CMD_MOVE_PREV:
                lRet = pSet->CDaoRecordset::MovePrev(); break;
            case DAO_CMD_EDIT:
                pSet->CDaoRecordset::Edit(); break;
            case DAO_CMD_UPDATE:
                pSet->CDaoRecordset::Update(); break;
            case DAO_CMD_DELETE:
                pSet->CDaoRecordset::Delete(); break;
            case DAO_CMD_ADDNEW:
                pSet->CDaoRecordset::AddNew(); break;
            // ... 其他命令
        }
    } catch (CDaoException* e) {
        lRet = e->m_nAfxDaoError;
        strErr = e->m_strError;
        e->Delete();
    } catch (CException* e) {
        TCHAR buf[256]; e->GetErrorMessage(buf, 256);
        strErr = buf; lRet = -1;
        e->Delete();
    }

    pParams->lResult = lRet;
    pParams->strError = strErr;
    SetEvent(pParams->hEvent); // 通知呼叫執行緒完成
    return 0;
}

4. 衍生類別使用範例

// MySet.h
class CMySet : public CMultiDaoRecordSet {
    DECLARE_DYNAMIC(CMySet)
public:
    CMySet(CDaoDatabase* pdb = NULL) : CMultiDaoRecordSet(pdb) {}
    // 宣告欄位...
};

// MySet.cpp
IMPLEMENT_DYNAMIC(CMySet, CMultiDaoRecordSet)
CMySet::CMySet(CDaoDatabase* pdb) : CMultiDaoRecordSet(pdb) {}

// 背景執行緒中安全使用
UINT WorkerThread(LPVOID pParam) {
    CMySet set(&db);
    set.Open();           // 自動封送至 GUI 執行緒
    while (!set.IsEOF()) {
        // 讀取欄位...
        set.MoveNext();   // 自動封送
    }
    set.Close();
    return 0;
}

進階優化:非同步模式與完成回調

同步等待 WaitForSingleObject 會阻塞背景執行緒。若需高併發,可改為:

  • 背景執行緒 PostMessage 後立即返回,不等待。
  • 主執行緒完成後 PostThreadMessage 通知背景執行緒(需背景執行緒有訊息迴圈)。
  • 或使用 完成回調函式指標 存放於 MultiDaoParam,主執行緒直接呼叫。

注意事項與限制

  • 效能開銷:每次 DAO 操作皆涉及跨執行緒封送,高頻操作(如逐筆 MoveNext)效能差。建議批次讀取至記憶體結構,再由背景執行緒處理。
  • 指標有效性:傳遞給主執行緒的 CMultiDaoRecordSet* 必須確保生命週期覆蓋操作期間,避免懸空指標。
  • DAO 版本:DAO 3.5/3.6 (Access 97/2000) 行為一致;Access 2007+ 需改用 ACE OLEDB + ADO。
  • 替代方案:新專案強烈建議直接使用 ADO(_RecordsetPtr 支援自由執行緒)或 ODBC,避開 DAO 執行緒限制。

總結

CMultiDaoRecordSet 透過 RegisterWindowMessage 訊息封送,將 DAO 的單執行緒限制轉化為透明的跨執行緒調用。此模式適用於維護既有 DAO 專案、需在背景執行緒存取資料庫的情境。對於新開發,優先選擇 ADO/ODBC 可從架構層面消除此類問題。

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