【问题标题】:Sync object to wait without blocking UI in C++ (C++ Builder)在 C++ 中同步对象以等待而不阻塞 UI(C++ Builder)
【发布时间】:2016-06-09 00:08:47
【问题描述】:

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

  • 只有在当前执行完成后,才允许重新输入关键代码(由相同另一个线程),
  • UI 线程在等待另一个当前正在执行关键代码的线程时不能被冻结。

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

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

类声明/定义:

#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();
}

【问题讨论】:

    标签: c++ multithreading winapi c++builder


    【解决方案1】:

    我建议用简单的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;
    }
    

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

    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;
    }
    

    【讨论】:

    • 非常感谢您的详细回答。我非常感谢。
    • __thread var 解决了“等待”的问题。但是,如果关键代码允许处理窗口消息,当线程正在执行代码时,线程本身可能会通过尝试执行/重新输入代码来中断它。在这种情况下,我认为线程永远无法释放信号量。我说的对吗?
    猜你喜欢
    • 2021-05-19
    • 2016-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-19
    • 1970-01-01
    相关资源
    最近更新 更多