Как правильно удалить указатель на функцию обратного вызова.

  1. У меня есть MainProgram.exe, который вызывает MyDll.dll и использует curl для получения данных о функции обратного вызова.
  2. Я завернул curl в функцию CurlGetData, которая создает экземпляр curl и выполняет curl_easy_perform.

Вот мой код:

//Interface class to derive from
class  ICurlCallbackHandler
{
public:
    virtual size_t CurlDataCallback( void* pData, size_t tSize ) = 0;
};

//Class that implements interface
class CurlCallbackHandler : public ICurlCallbackHandler
{
public:
    bool m_exit = false;

    virtual size_t CurlDataCallback( void* pData, size_t tSize ) override
    {
        if(m_exit)
            return CURL_READFUNC_ABORT;

       // do stuff with the curl data

        return tSize;
    }
}


CurlCallbackHandler *m_curlHandler;

//Create an instance of above class in my dll constructor
MyDll:MyDll()
{
    m_curlHandler = new CurlCallbackHandler();
}

//Cleanup above class in my dll destructor
MyDll:~MyDll()
{
    delete m_curlHandler;
    m_curlHandler = nullptr;
}

//Function to start receiving data asynchronously  
void MyDll::GetDataAsync()
{
    std::async([=]
    {
        //This will receive data in a new thread and call CurlDataCallback above
        //This basically calls easy_perform
        CurlGetData(m_curlHandler);
    }
}

//Will cause the curl callback to return CURL_READFUNC_ABORT
void MyDll::StopDataAsync()
{
    m_curlHandler->m_exit = true;
}

Функция GetDataAsync вызывается из моей основной программы, и она в основном вызывает curl_easy_perform и использует m_curlHandler в качестве своей функции обратного вызова, которая вызывает обратно в CurlDataCallback.

Это все работает нормально, но всякий раз, когда моя основная программа завершает работу, она вызывает MyDll::StopDataAsync, который останавливает обратный вызов данных curl, а затем вызывается деструктор MyDll, который очищает m_curlHandler.

Но я обнаружил, что в этот момент curl еще не закончил с этим обратным вызовом, и программа падает, так как m_curlHandler был удален, но обратный вызов curl в новом асинхронном потоке все еще использует его.

Иногда он нормально закрывается, но иногда происходит сбой из-за того, что curlcallback пытается получить доступ к указателю, удаленному деструктором.

Как мне лучше очистить m_curlHandler? Я хочу избегать тайм-аутов ожидания, так как это повлияет на производительность моей основной программы.

1 ответ

Согласно стандарту C++ MyDll::GetDataAsync() функция не должна возвращаться немедленно, она должна блокироваться до тех пор, пока асинхронный поток не завершится, что эффективно сделает операцию синхронной. Однако я считаю, что Microsoft намеренно нарушила эту часть std::async спецификацией, так что на самом деле он возвращается немедленно, и вы можете уничтожить обратный вызов, пока асинхронный поток все еще использует его (именно этой проблемы можно было бы избежать, если бы реализация Microsoft следовала стандарту!)

Решение состоит в том, чтобы держать std::future тот std::async возвращает, а затем блокирует это будущее (что обеспечивает завершение асинхронного потока) перед уничтожением обратного вызова.

class MyDLL
{
    std::future<void> m_future;
    ...
};

MyDll:~MyDll()
{
    StopDataAsync();
    m_future.get();        // wait for async thread to exit.
    delete m_curlHandler;  // now it's safe to do this
}

//Function to start receiving data asynchronously  
void MyDll::GetDataAsync()
{
    m_future = std::async([=]
    {
        //This will receive data in a new thread and call CurlDataCallback above
        //This basically calls easy_perform
        CurlGetData(m_curlHandler);
    }
}

NB ваш m_exit участник должен быть std::atomic<bool> (или вы должны использовать мьютекс для защиты всех операций чтения и записи в него), иначе ваша программа имеет гонку данных и поэтому имеет неопределенное поведение.

Я бы также использовал std::unique_ptr<CurlCallbackHandler> за m_curlHandler,

Я хочу избегать тайм-аутов ожидания, так как это повлияет на производительность моей основной программы.

Приведенное выше решение заставит вашего деструктора ждать, но только до тех пор, пока обратный вызов не заметит, что m_exit == true и заставить асинхронный поток прекратить работу. Это означает, что вы ожидаете только столько времени, сколько необходимо, а не больше, в отличие от тайм-аутов, которые означают, что вы должны угадать, как долго это "достаточно долго", а затем, вероятно, добавить немного больше, чтобы быть в безопасности.

Другие вопросы по тегам