如何避免用户自定义Autoit回调函数的并发调用?
问题描述
我正在修改Boost代码以适配AutoIt,原项目为WebSocketAsioAHK,我的版本分支是dp_update。当前需要解决的核心问题是:如何防止用户提供的AutoIt回调函数被并发调用。
现有on_read回调代码如下:
/// Callback registered by async_read. It calls user registered callback to actually process the data. And then issue another async_read to wait for data from server again. /// \param ec instance of error code /// \param bytes_transferred void on_read( beast::error_code ec, std::size_t bytes_transferred) { if(EnableVerbose) { boost::lock_guard<boost::mutex> guard(mtx_); std::wcout << L"<WsDll-" ARCH_LABEL "> in on read" << std::endl; } boost::ignore_unused(bytes_transferred); { boost::lock_guard<boost::mutex> guard(mtx_); if(!Is_Connected) { return; } } // error occurs if (ec) { if(on_fail_cb) on_fail_cb(L"read"); return fail(ec, L"read"); } const std::string data = beast::buffers_to_string(buffer_.data()); const std::wstring wdata(data.begin(), data.end()); if(EnableVerbose) { boost::lock_guard<boost::mutex> guard(mtx_); std::wcout << L"<WsDll-" ARCH_LABEL "> received[" << bytes_transferred << L"] " << wdata << std::endl; } // The next section is where my issue resides if (on_data_cb) on_data_cb(wdata.c_str(), wdata.length()); buffer_.consume(buffer_.size()); if(EnableVerbose) { boost::lock_guard<boost::mutex> guard(mtx_); std::wcout << L"<WsDll-" ARCH_LABEL "> issue new async_read in on_read" << std::endl; } ws_.async_read( buffer_, beast::bind_front_handler( &session::on_read, shared_from_this())); // Close the WebSocket connection // ws_.async_close(websocket::close_code::normal, // beast::bind_front_handler( // &session::on_close, // shared_from_this())); }
其中if (on_data_cb) on_data_cb(wdata.c_str(), wdata.length());是触发AutoIt回调的代码,需要保证这段代码不会被同时执行多次。
解决方案
针对这个问题,提供两种适配现有代码的可行方案,可根据回调执行耗时和性能需求选择:
方案一:互斥锁直接保护回调调用
利用你已有的mtx_互斥量,将回调调用逻辑包裹在锁范围内,强制同一时刻只有一个线程能执行回调:
// 替换原有的回调调用代码 if (on_data_cb) { boost::lock_guard<boost::mutex> guard(mtx_); on_data_cb(wdata.c_str(), wdata.length()); }
注意:如果AutoIt回调执行耗时较长,会阻塞后续async_read的触发,可能导致WebSocket消息接收延迟或积压,这种情况下更推荐方案二。
方案二:串行任务队列异步执行回调
借助Boost Asio的strand(串行执行器),将回调任务投递到串行队列中执行,既避免并发调用,又不会阻塞WebSocket的消息接收流程:
- 首先在
session类中添加strand成员变量:
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
- 在
session构造函数中初始化strand:
session(boost::asio::io_context& ioc) : ws_(ioc), strand_(boost::asio::make_strand(ioc)) { // 保留原有初始化逻辑 }
- 修改回调调用代码,将任务投递到
strand中:
if (on_data_cb) { // 复制数据避免生命周期失效问题 std::wstring data_copy = wdata; boost::asio::post(strand_, [this, data_copy]() { if (on_data_cb) { on_data_cb(data_copy.c_str(), data_copy.length()); } }); }
此方案中,strand会保证所有回调任务按顺序串行执行,不会出现并发调用,同时不会阻塞async_read的发起,不影响WebSocket的接收性能。
额外注意事项
- AutoIt回调函数本身大概率不是线程安全的,无论采用哪种方案,都要确保回调执行期间,AutoIt端不会有其他线程操作共享资源。
- 若选择方案一,务必评估回调的执行耗时,避免因长时间锁阻塞导致WebSocket连接异常。
内容的提问来源于stack exchange,提问作者Dan Pollak
相关产品推荐
相关产品推荐

