【问题标题】:How to properly delete a pointer to callback function.如何正确删除指向回调函数的指针。
【发布时间】:2015-02-23 12:30:57
【问题描述】:
  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?我想避免等待超时,因为这会影响我的主程序的性能。

【问题讨论】:

    标签: c++ curl libcurl stdasync


    【解决方案1】:

    根据 C++ 标准,MyDll::GetDataAsync() 函数不应该立即返回,它应该阻塞直到异步线程完成,这将有效地使操作同步。但是我相信微软故意违反了std::async 规范的这一部分,所以实际上它确实会立即返回,并且您可以在异步线程仍在使用它时销毁回调(这正是可以避免的问题,如果Microsoft 实施遵循标准!)

    解决方案是保留std::async 返回的std::future,然后在销毁回调之前阻塞该未来(确保异步线程已完成)。

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

    注意你的 m_exit 成员应该是 std::atomic&lt;bool&gt; (或者你应该使用互斥锁来保护对它的所有读取和写入),否则你的程序会发生数据竞争,因此会有未定义的行为。

    我也会将std::unique_ptr&lt;CurlCallbackHandler&gt; 用于m_curlHandler

    我想避免等待超时,因为这会影响我的主程序的性能。

    上面的解决方案导致你的析构函数等待,但只要回调注意到m_exit == true并导致异步线程停止运行。这意味着您只等待必要的时间,不再等待,与超时不同,超时意味着猜测“足够长”的时间,然后可能会添加更多以确保安全。

    【讨论】:

      猜你喜欢
      • 2021-12-29
      • 2010-12-01
      • 2018-09-12
      • 2018-08-09
      • 1970-01-01
      • 1970-01-01
      • 2021-06-25
      • 1970-01-01
      • 2013-04-10
      相关资源
      最近更新 更多