在 C++ (C++ Builder) 中同步对象等待而不阻塞 UI

Sync object to wait without blocking UI in C++ (C++ Builder)

我有一个关于 C++ 多线程的任务,其中有一个关键代码块。主要要求如下:

所以我在 C++ Builder 中创建了以下 class 来满足要求。 你觉得有什么问题吗?

非常感谢您提前抽出时间!

Class声明/定义:

#include "windows.h"
#include <vector>
#include <algorithm>

class TSyncObject
{
private:
    DWORD WorkingThreadId;
    std::vector<DWORD> WaitingThreadIds;
    TCriticalSection *Section;
    HANDLE Event;
//---------------------------------------------------------------------------
    bool CanThreadWait(const DWORD ThreadId)
    {
        bool CanWait;
        Section->Enter();
        try
        {
            bool AlreadyWaiting =
                std::find(WaitingThreadIds.begin(), WaitingThreadIds.end(), ThreadId) != WaitingThreadIds.end();
            CanWait = !AlreadyWaiting && ThreadId != WorkingThreadId && WorkingThreadId;
            if (CanWait)
            {
                WaitingThreadIds.push_back(ThreadId);
            }
        }
        __finally
        {
            Section->Leave();
        }
        return CanWait;
    }
//---------------------------------------------------------------------------
    void Acquire(const DWORD ThreadId)
    {
        Section->Enter();
        try
        {
            WorkingThreadId = ThreadId;
            std::vector<DWORD>::iterator Pos =
                std::find(WaitingThreadIds.begin(), WaitingThreadIds.end(), ThreadId);
            if (Pos != WaitingThreadIds.end())
            {
                WaitingThreadIds.erase(Pos);
            }
        }
        __finally
        {
            Section->Leave();
        }
    }
//---------------------------------------------------------------------------
    void HandleError()
    {
        Section->Enter();
        try
        {
            if (GetCurrentThreadId() == WorkingThreadId ||
                (WaitingThreadIds.empty() && !WorkingThreadId))
            {
                WorkingThreadId = 0;
                SetEvent(Event);
            }
        }
        __finally
        {
            Section->Leave();
        }
    }
//---------------------------------------------------------------------------


public:
//---------------------------------------------------------------------------
    enum TThreadAcquire {Acquired, WaitEjected, AppTerminated, Failed};
//---------------------------------------------------------------------------
    TSyncObject() :
        WorkingThreadId(0),
        Section(new TCriticalSection()),
        Event(CreateEventW(0, 0, 1, 0))
    {
    }
//---------------------------------------------------------------------------
    virtual ~TSyncObject()
    {
        CloseHandle(Event);
        delete Section;
    }
//---------------------------------------------------------------------------
    TThreadAcquire Acquire()
    {
        try
        {
            DWORD CurrentThreadId = GetCurrentThreadId();
            if (WaitForSingleObject(Event, 0) != WAIT_OBJECT_0)
            {
                if (!CanThreadWait(CurrentThreadId))
                {
                    return WaitEjected;
                }
                while (!Application->Terminated &&
                    (MsgWaitForMultipleObjects(1, &Event, 0, INFINITE, QS_ALLINPUT) - WAIT_OBJECT_0))
                {
                    Application->ProcessMessages();
                }
                if (Application->Terminated)
                {
                    return AppTerminated;
                }
            }
            Acquire(CurrentThreadId);
            return Acquired;
        }
        catch (...)
        {
            HandleError();
            return Failed;
        }
    }

//---------------------------------------------------------------------------
    void Release()
    {
        Section->Enter();
        try
        {
            WorkingThreadId = 0;
            SetEvent(Event);
        }
        __finally
        {
            Section->Leave();
        }
    }
//---------------------------------------------------------------------------
};

我会这样使用:

TSyncObject Sync; // Global for all threads
//...

一个线程使用这样的对象:

TSyncObject::TThreadAcquire Acq = Sync.Acquire();
try
{
    if (Acq == TSyncObject::WaitEjected)
    {
        //...
        return;
    }
    else if (Acq == TSyncObject::AppTerminated)
    {
        //...
        return;
    }
    else if (Acq == TSyncObject::Failed)
    {
        //...
        return;
    }

    // critical code block

}
__finally
{
    Sync.Release();
}

我建议用简单的 semaphore 代替所有事件和临界区处理。

试试像这样的东西:

MySyncObject.hpp

#ifndef MySyncObjectH
#define MySyncObjectH

#include "windows.h"

class TSyncObject
{
private:
    HANDLE Semaphore;

public:
    enum TThreadAcquire {Acquired, WaitEjected, AppTerminated, Failed};

    TSyncObject();
    ~TSyncObject();

    TThreadAcquire Acquire();
    void Release();
};

#endif

MySyncObject.cpp

#include "MySyncObject.hpp"

bool __thread SyncState = 0;

TSyncObject::TSyncObject()
    : Semaphore(NULL)
{
    Semaphore = CreateSemaphore(NULL, 1, 1, NULL);
    if (!Semaphore) RaiseLastOSError();
}

TSyncObject::~TSyncObject()
{
    CloseHandle(Semaphore);
}

TSyncObject::TThreadAcquire TSyncObject::Acquire()
{
    DWORD dwRet = WaitForSingleObject(Semaphore, 0);
    if (dwRet == WAIT_TIMEOUT)
    {
        if (SyncState != 0)
            return WaitEjected;

        SyncState = 1;

        while (!Application->Terminated)
        {
            dwRet = MsgWaitForMultipleObjects(1, &Semaphore, FALSE, INFINITE, QS_ALLINPUT);
            if ((dwRet == WAIT_OBJECT_0) || (dwRet == WAIT_FAILED))
                break;

            if (dwRet == (WAIT_OBJECT_0+1))
            {
                try
                {
                    Application->ProcessMessages();
                }
                catch (...)
                {
                    //...
                }
            }
        }
    }

    if (dwRet != WAIT_OBJECT_0)
    {
        SyncState = 0;

        if (Application->Terminated)
            return AppTerminated;

        return Failed;
    }

    SyncState = 2;
    return Acquired;
}

void TSyncObject::Release()
{
    ReleaseSemaphore(Semaphore, 1, NULL);
    SyncState = 0;
}

然后像这样使用它:

TSyncObject Sync;

...

TSyncObject::TThreadAcquire Acq = Sync.Acquire();
if (Acq == TSyncObject::Acquired)
{
    try
    {
        // critical code block...
    }
    __finally
    {
        Sync.Release();
    }
}
else
{
    if (Acq == TSyncObject::WaitEjected)
    {
        //...
    }
    else if (Acq == TSyncObject::AppTerminated)
    {
        //...
    }
    else // TSyncObject::Failed
    {
        //...
    }
    return;
}

话虽如此,您可以考虑将 class 编写为单例而不是声明全局变量:

MySyncObject.hpp

#ifndef MySyncObjectH
#define MySyncObjectH

#include "windows.h"

class TSyncObject
{
private:
    HANDLE Semaphore;

    TSyncObject();

public:
    enum TThreadAcquire {Acquired, WaitEjected, AppTerminated, Failed};

    ~TSyncObject();

    static TSyncObject& Instance();

    TThreadAcquire Acquire();
    void Release();
};

#endif

MySyncObject.cpp

#include "MySyncObject.hpp"

...

static TSyncObject& TSyncObject::Instance()
{
    static TSyncObject inst;
    return inst;
}

...

TSyncObject::TThreadAcquire Acq = TSyncObject::Instance().Acquire();
if (Acq == TSyncObject::Acquired)
{
    try
    {
        // critical code block...
    }
    __finally
    {
        TSyncObject::Instance().Release();
    }
}
else
{
    if (Acq == TSyncObject::WaitEjected)
    {
        //...
    }
    else if (Acq == TSyncObject::AppTerminated)
    {
        //...
    }
    else // TSyncObject::Failed
    {
        //...
    }
    return;
}

或者:

MySyncObject.hpp

#ifndef MySyncObjectH
#define MySyncObjectH

#include "windows.h"

class TSyncObject
{
private:
    HANDLE Semaphore;

    TSyncObject();

    static TSyncObject& Instance();

public:
    enum TThreadAcquire {Acquired, WaitEjected, AppTerminated, Failed};

    ~TSyncObject();

    static TThreadAcquire Acquire();
    static void Release();
};

#endif

MySyncObject.cpp

#include "MySyncObject.hpp"

...

TSyncObject::TThreadAcquire TSyncObject::Acquire()
{
    TSyncObject &inst = Instance();
    // use inst.Semaphore as needed...
}

void TSyncObject::Release()
{
    TSyncObject &inst = Instance();
    // use inst.Semaphore as needed...
}

TSyncObject::TThreadAcquire Acq = TSyncObject::Acquire();
if (Acq == TSyncObject::Acquired)
{
    try
    {
        // critical code block...
    }
    __finally
    {
        TSyncObject::Release();
    }
}
else
{
    if (Acq == TSyncObject::WaitEjected)
    {
        //...
    }
    else if (Acq == TSyncObject::AppTerminated)
    {
        //...
    }
    else // TSyncObject::Failed
    {
        //...
    }
    return;
}