C++ Wait But Allow Events To Fire

2019-08-09 21:35发布

问题:

Building a SignalR C++ client using Visual Studio 2013, I am starting with the working sample code from NuGet Package Microsoft.AspNet.SignalR.Client.Cpp.v120.WinDesktop, source here

Reviewing the library source it seems to me the event handling processes are based on the Concurrency Runtime (pplx::task) which relies on C++11 features

void chat(const utility::string_t& name)
{
    signalr::hub_connection connection{ U("https://testsite") };
    auto proxy = connection.create_hub_proxy(U("ChatHub"));
    proxy.on(U("broadcastMessage"), [](const web::json::value& m)
    {
        ucout << std::endl << m.at(0).as_string() << U(" wrote:") << m.at(1).as_string() << std::endl << U("Enter your message: ");
    });

    connection.start()
        .then([proxy, name]()
    {
        for (;;)
        {
            utility::string_t message;
            std::getline(ucin, message);

            if (message == U(":q"))
            {
                break;
            }

            send_message(proxy, name, message);
        }
    })
        .then([&connection]() // fine to capture by reference - we are blocking so it is guaranteed to be valid
    {
        return connection.stop();
    })
        .then([](pplx::task<void> stop_task)
    {
        try
        {
            stop_task.get();
            ucout << U("connection stopped successfully") << std::endl;
        }
        catch (const std::exception &e)
        {
            ucout << U("exception when starting or stopping connection: ") << e.what() << std::endl;
        }
    }).get();
}

I want to eliminate the "user input" component; and instead quit loop when a particular "broadcastMessage" has been received.

If I replace the for loop with a sleep statement, the broadcastMessage event stops firing.

If I use the for loop without the getline, set bComplete to true when done, it works the way I want but causes high CPU usage (obviously)

for (;;)
{
if (bComplete) break;
}

Ideally I want connection to start, and then just wait until the broadcastMessage events signals to close the connection. In addition the "chat" function shouldn't return until connection has closed.

回答1:

I can see in your answer that you've already discovered Windows event objects; however, if you were looking for a C++11 platform-independent solution, consider std::condition_variable!

unsigned int accountAmount;
std::mutex mx;
std::condition_variable cv;

void depositMoney()
{
    // go to the bank etc...
    // wait in line...
    {
        std::unique_lock<std::mutex> lock(mx);
        std::cout << "Depositing money" << std::endl;
        accountAmount += 5000;
    }
    // Notify others we're finished
    cv.notify_all();
}
void withdrawMoney()
{
    std::unique_lock<std::mutex> lock(mx);
    // Wait until we know the money is there
    cv.wait(lock);
    std::cout << "Withdrawing money" << std::endl;
    accountAmount -= 2000;
}
int main()
{
    accountAmount = 0;
    std::thread deposit(&depositMoney);
    std::thread withdraw(&withdrawMoney);
    deposit.join();
    withdraw.join();
    std::cout << "All transactions processed. Final amount: " << accountAmount << std::endl;
    return 0;
}

In this example we make two threads: one to deposit money into the account and one to withdraw money. Because it's possible for the thread to withdraw the money to run first, especially because there's more processing involved with depositMoney(), we need to wait until we know the money is there. We lock our thread before accessing the money, and then tell the condition_variable what we are waiting for. The condition_variable will unlock the thread, and once the money has been deposited and notify_all() is called we'll be re-awoken to finish processing our logic.

Note that it's possible to do the exact same using the Windows event objects. Instead of std::condition_variable::wait() and std::condition_variable::notify_all() you'd use SetEvent() and WaitForSingleObject(). This is platform-independent though.



回答2:

I got this working using WinAPI WaitForSingleObject:

HANDLE hEvent;
    void chat(const utility::string_t& name)
    {
        signalr::hub_connection connection{ U("https://testsite") };
        auto proxy = connection.create_hub_proxy(U("ChatHub"));
        proxy.on(U("broadcastMessage"), [](const web::json::value& m)
        {
            ucout << std::endl << m.at(0).as_string() << U(" wrote:") << m.at(1).as_string() << std::endl;
        if (m.at(1).as_string() == L"quit")
            {
                SetEvent(hEvent);
            }
        });

    hEvent = CreateEvent(0, TRUE, FALSE, 0);

        connection.start()
            .then([proxy, name]()
        {
            WaitForSingleObject(hEvent, INFINITE);
        })
            .then([&connection]() // fine to capture by reference - we are blocking so it is guaranteed to be valid
        {
            return connection.stop();
        })
            .then([](pplx::task<void> stop_task)
        {
            try
            {
                stop_task.get();
                ucout << U("connection stopped successfully") << std::endl;
            }
            catch (const std::exception &e)
            {
                ucout << U("exception when starting or stopping connection: ") << e.what() << std::endl;
            }`enter code here`
        }).get();
    }