简体   繁体   English

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

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

I have a task on multi-threading in C++ in which there is a critical code block. 我有一个C ++中的多线程任务,其中有一个关键代码块。 Main requirements are the followings: 主要要求如下:

  • the critical code can be allowed to be re-entered (by the same or another thread) only after its current execution is finished, 仅在当前执行完成后才允许重新输入关键代码(由同一线程或另一个线程),
  • UI thread mustn't be frozen while it is waiting for another thread currently executing the critical code. 在等待当前正在执行关键代码的另一个线程时,不得冻结UI线程。

So I created the following class in C++ Builder to fulfill the requirements. 因此,我在C ++ Builder中创建了以下来满足要求。 Do you think there is any issue in it? 您是否认为其中存在任何问题?

Many thanks for your time in advance! 非常感谢您提前抽出宝贵的时间!

Class declaration / definition: 类声明/定义:

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

I will use it like this: 我将这样使用它:

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

A thread uses the object like this: 线程使用这样的对象:

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

I would suggest replacing all of the event and criticalsection handling with a simple semaphore instead. 我建议用一个简单的信号量代替所有的事件和临界区处理。

Try something more like this: 尝试更多类似这样的方法:

MySyncObject.hpp 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 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;
}

Then use it like this: 然后像这样使用它:

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

With that said, you might consider writing the class as a singleton instead of declaring a global variable: 话虽如此,您可以考虑将类写为单例而不是声明全局变量:

MySyncObject.hpp 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 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;
}

Alternatively: 或者:

MySyncObject.hpp 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 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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM