c++异步回调函数引用传递空指针异常
c++异步回调函数引用传递空指针异常
问题描述
最近使用 c++ / qt 开发的一个桌面应用,运行到一处异步执行python脚本任务的方法处报错:
进程已结束,退出代码-1073741819 (0xC0000005)
此处是单独开一个线程异步执行一个python脚本后,回调 UI 线程传来的回调函数将结果返回给 UI 线程,大致代码如下:
void TestCaseProject::initProTestCasesEnvAsync(const std::function<void(std::vector<std::pair<std::string, Json::Value>>)>& _callback) {
std::thread t{[&] () {
doWithSetRunning([&]() {
auto result = this->initProTestCasesEnv();
_callback(result);
});
}};
t.detach();
}
void callBackFunc(const std::vector<std::pair<std::string, Json::Value>>& rps) {
// do some things
}
// UI线程某函数调用initProTestCasesEnvAsync
void uiFunc() {
// create project and other code
project->initProTestCasesEnvAsync(callBackFunc);
// do other things
}
解决方案
问题解读
搜索进程已结束,退出代码-1073741819 (0xC0000005),网上对其的准确描述很少,于是进行总结:
退出代码就是执行的程序退出时的返回值,如
main函数直接返回、调用程序退出的函数void exit(int _Code)、有未解决的异常从程序抛出到系统后返回系统定义的错误退出码等,通常是一个十六进制 int 值。退出代码中括号内的才是实际的十六进制退出代码(一般使用这个),前面是其十进制表示(因为起始有一个十六进制数 c 所以变成负数,类似一个标识,用来区分各系列错误代码)。
错误代码无法确定错误的详细信息,只能大致进行判断,具体情况需要进一步分析代码上下文,或者捕捉异常、进行调试来确定。
错误退出代码一般由未处理的异常触发,而不是直接退出程序并返回该代码。
在Windows系统下,错误代码定义在头文件
<winerror.h>
举一反三,在其他操作系统上也有定义错误代码的位置,但定义位置可能不同,大家可以自行查找。不过,错误代码及其含义在各系统平台定义基本是一致的,不会有太大出入。
问题分析
1. 错误代码分析
使用微软错误代码查找工具查找错误代码0xC0000005,结果如下:
PS D:\tools> .\Err_6.4.5.exe C0000005
# for hex 0xc0000005 / decimal -1073741819
ISCSI_ERR_SETUP_NETWORK_NODE iscsilog.h
# Failed to setup initiator portal. Error status is given in
# the dump data.
STATUS_ACCESS_VIOLATION ntstatus.h
# The instruction at 0x%p referenced memory at 0x%p. The
# memory could not be %s.
USBD_STATUS_DEV_NOT_RESPONDING usb.h
# as an HRESULT: Severity: FAILURE (1), FACILITY_NONE (0x0), Code 0x5
# for hex 0x5 / decimal 5
WINBIO_FP_TOO_FAST winbio_err.h
# Move your finger more slowly on the fingerprint reader.
# as an HRESULT: Severity: FAILURE (1), FACILITY_NULL (0x0), Code 0x5
ERROR_ACCESS_DENIED winerror.h
# Access is denied.
# 5 matches found for "C0000005"
经过分析,其中,第5条查找结果(第20行)就是问题的主要原因(主要看定义在<winerror.h>中的代码)。
ERROR_ACCESS_DENIED:Access is denied.表示访问被拒绝,这是访问了无权访问的内存地址空间,常见的场景有:
- 空指针
- 数组越界
- 释放内存后产生的野指针
以上场景都会造成未定义行为,并可能抛出异常触发ERROR_ACCESS_DENIED错误并退出。
2. 代码调试
在调试模式运行,查看抛出的异常信息如下:
terminate called after throwing an instance of 'std::bad_function_call'
what(): bad_function_call
异常std::bad_function_call在调用空的函数对象(std::function)时抛出。空的函数对象一般情况是未给函数对象赋值或赋值null。
我们回到问题描述的代码部分,回调函数的函数对象是 UI 主线程中某个函数将全局函数的指针传入构造的,initProTestCasesEnvAsync方法的参数是常量引用,被线程执行的 lambda 函数捕捉其引用,又被线程执行函数内的doWithSetRunning的 lambda 函数参数捕捉其引用,并在其内调用该函数对象。
经过单行调试,发现异常就是在异步线程执行该回调函数对象是触发的。
机智的小伙伴可能已经发现,根据上面描述的变量传递关系,最终执行的回调函数对象就是 UI 线程调用initProTestCasesEnvAsync时传入callBackFunc函数指针并构建的局部函数对象的引用。正常一个串行执行的程序,这样自然没有问题,在initProTestCasesEnvAsync返回时已完成callBackFunc的调用。但若创建回调函数对象与执行该回调函数对象处在不同线程,就会发生局部的回调函数对象因为其上下文的函数异步执行结束而释放内存,导致执行线程保存的回调函数的引用内部空指针,调用时触发std::bad_function_call异常。
问题解决
知道了问题所在,解决起来就很简单了。一个异步执行的线程,除了全局、动态分配的内存等非局部的对象可共享内存数据进行读写,局部数据都要进行数据拷贝以实现隔离。基于上面的理论,这里应该是 UI 线程调用时传来的局部变量执行拷贝,动态申请的对象直接引用。由于实际执行的代码体中只使用了this指针和传来的函数参数,所以都执行拷贝即可。
在任务线程的 lambda 执行函数中,将捕捉引用改为捕捉值,内部的doWithSetRunning的 lambda 执行函数捕捉该异步线程捕捉值得到的拷贝的引用,即可实现非局部变量(this指向的内存对象)的共享与局部变量(_callback 回调函数对象)的隔离。
修改代码如下:
void TestCaseProject::initProTestCasesEnvAsync(const std::function<void(std::vector<std::pair<std::string, Json::Value>>)>& _callback) {
std::thread t{[=] () {
doWithSetRunning([&]() {
auto result = this->initProTestCasesEnv();
_callback(result);
});
}};
t.detach();
}
最后,提醒大家一定要注意 lambda 的引用传递的正确性,因为小编已经遇到多次这里的问题,而在异步场景下就更要注意对象传递过程中各对象的传递关系与生命周期了。
c++异步回调函数引用传递空指针异常的更多相关文章
- ArcGIS中使用异步回调函数查询图层Graphic
在我们的地图的操作中经常会有一些操作是需要通过画多边形或者画线来查找某一块区域内的特定的Graphics比如我们在做的交警的项目中通过框选来查找某一块区域中的摄像机,某一块区域中的警力.警情.警员等相 ...
- Python并发编程06 /阻塞、异步调用/同步调用、异步回调函数、线程queue、事件event、协程
Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件event.协程 目录 Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件 ...
- 前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply
一.快捷位置和尺寸属性 DOM已经提供给我们计算后的样式,但是还是觉得不方便,因为计算后的样式属性值都是字符串类型. 不能直接参与运算. 所以DOM又提供了一些API:得到的就是number类型的数据 ...
- C#IAsyncResult异步回调函数的解释
问题:IAsyncResult ar 是如何通过ar.AsyncState强制转换成TCPClientState类型 答:实例中使用的方法如下 我给IAsyncResult ar传入了TCPClien ...
- WPF如何获得变量异步回调函数时产生的异步回调
有这样的问题,WPF当使用异步回调,需要使用产生的异步变量中的回调函数.数据库中查询诸如异步函数来获得一DataTable.怎样传递给回调函数呢? [方案一]使用全局变量 非常easy想到的是用全局变 ...
- C#--委托的同步,异步,回调函数
原文地址 同步调用 委托的Invoke方法用来进行同步调用.同步调用也可以叫阻塞调用,它将阻塞当前线程,然后执行调用,调用完毕后再继续向下进行. using System; using System. ...
- python 管道 事件(Event) 信号量 进程池(map/同步/异步)回调函数
####################总结######################## 管道:是进程间通信的第二种方式,但是不推荐使用,因为管道会导致数据不安全的情况出现 事件:当我运行主进程的 ...
- form.submit(回调函数)——引用jq-form.js
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...
- JS的异步回调函数
hi :)几日不见,趁着周末和父母在广州走走逛逛,游山玩水,放松身心,第一天上班就被一个问题难住了,不废话,以下是关于JS函数回调方面的知识,今天的查阅看的也是一知半解,摘录下来日后慢慢琢磨! js中 ...
- python异步回调函数的实现
#coding:utf-8 from socket import * import time #简单的服务器程序 监听用户连接,接收用户发来的信息,并返回反馈 def main(): HOST = & ...
随机推荐
- LVGL一键打包图片工具,全部图片打包成一个bin文件,支持nor flash XIP模式下直接访问数据显示
最近做工程项目,需要用到LVGL,但是搜了很长时间没有看到合适的图片打包工具,大多都是生成数组或者单个的bin文件,这样烧录到nor flash很麻烦 后来看到一篇博客,博主的想法与我类似,不过他后面 ...
- java 高效递归查询树 find_in_set 处理递归树
建表语句 DROP TABLE IF EXISTS `sys_dept`; CREATE TABLE `sys_dept` ( `id` bigint(20) NOT NULL AUTO_INCREM ...
- 解决方案 | 预装win11如何退回win10?
0.定义 本文所说的[退回]并不指的是win10升级后的变成win11再变为win10的退回.退回应该理解为[降级],或者叫作返回上一个版本.本文的适用范围局限于,预装系统是win11,想要不通过u盘 ...
- Django日期字段默认值default=timezone.now
如果你确实希望默认值是当前日期和时间,Django 提供了一个方便的函数 django.utils.timezone.now 来实现这一目的. 你可以这样调整你的模型,以使用当前日期和时间作为默认值: ...
- 渐变边框文字效果?CSS 轻松拿捏!
今天,有个群友问了我这么一个问题,如果不想切图,是否有办法实现带渐变边框的字体效果?如下所示: 本文,就将尝试一下,在 CSS 中,我们可以如何尽可能的实现这种渐变边框字体效果. 元素叠加 首先,比较 ...
- 【SpringBoot】10 Web开发 Part1 静态资源
使用SpringBoot创建工程的方式: 1.在IDEA集成的Boot官网选项中点选可能需要的框架环境即可 2.SpringBoot已经设置好了这些场景,只需要配置文件中指定少量配置就可以运行起来 3 ...
- 【DataBase】MySQL 31 游标
游标 Cursor 游标是用来存储查询的结果集的数据类型,也称为是光标 在存储过程和函数中可以使用光标对结果集进行循环的处理 光标的使用包括1.声明,2.开启,3.关闭,4.Fetch 游标仅用于存储 ...
- 【Tutorial C】05 操作符 & 表达式
基本运算符 C使用运算符(operator)来代表算术运算.例如,+运算符可以使它两侧的值加在一起. 如果您觉得术语"运算符"听起来比较奇怪,那么请您记住那些东西总得有个名称. 与 ...
- 【Maven】下载安装(Linux)
Maven官网下载地址: http://maven.apache.org/download.cgi 点选这个压缩包 wget或者上传都行 解压 tar -zxvf apache-maven-3.6.3 ...
- 【ECharts】02 饼图
饼状图: <!-- 为ECharts准备一个具备大小(宽高)的Dom --> <div id="main" style="width: 600px;he ...