2015-02-23 2 views
1
  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 в новом потоке async все еще использует его.

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

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

ответ

0

В соответствии со стандартом 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); 
    } 
} 

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

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

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

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