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>

    Windows错误代码详情可见官方文档说明。同时,微软官方提供了一个错误代码的含义查找工具,下载链接

  • 举一反三,在其他操作系统上也有定义错误代码的位置,但定义位置可能不同,大家可以自行查找。不过,错误代码及其含义在各系统平台定义基本是一致的,不会有太大出入。

问题分析

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++异步回调函数引用传递空指针异常的更多相关文章

  1. ArcGIS中使用异步回调函数查询图层Graphic

    在我们的地图的操作中经常会有一些操作是需要通过画多边形或者画线来查找某一块区域内的特定的Graphics比如我们在做的交警的项目中通过框选来查找某一块区域中的摄像机,某一块区域中的警力.警情.警员等相 ...

  2. Python并发编程06 /阻塞、异步调用/同步调用、异步回调函数、线程queue、事件event、协程

    Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件event.协程 目录 Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件 ...

  3. 前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

    一.快捷位置和尺寸属性 DOM已经提供给我们计算后的样式,但是还是觉得不方便,因为计算后的样式属性值都是字符串类型. 不能直接参与运算. 所以DOM又提供了一些API:得到的就是number类型的数据 ...

  4. C#IAsyncResult异步回调函数的解释

    问题:IAsyncResult ar 是如何通过ar.AsyncState强制转换成TCPClientState类型 答:实例中使用的方法如下 我给IAsyncResult ar传入了TCPClien ...

  5. WPF如何获得变量异步回调函数时产生的异步回调

    有这样的问题,WPF当使用异步回调,需要使用产生的异步变量中的回调函数.数据库中查询诸如异步函数来获得一DataTable.怎样传递给回调函数呢? [方案一]使用全局变量 非常easy想到的是用全局变 ...

  6. C#--委托的同步,异步,回调函数

    原文地址 同步调用 委托的Invoke方法用来进行同步调用.同步调用也可以叫阻塞调用,它将阻塞当前线程,然后执行调用,调用完毕后再继续向下进行. using System; using System. ...

  7. python 管道 事件(Event) 信号量 进程池(map/同步/异步)回调函数

    ####################总结######################## 管道:是进程间通信的第二种方式,但是不推荐使用,因为管道会导致数据不安全的情况出现 事件:当我运行主进程的 ...

  8. form.submit(回调函数)——引用jq-form.js

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

  9. JS的异步回调函数

    hi :)几日不见,趁着周末和父母在广州走走逛逛,游山玩水,放松身心,第一天上班就被一个问题难住了,不废话,以下是关于JS函数回调方面的知识,今天的查阅看的也是一知半解,摘录下来日后慢慢琢磨! js中 ...

  10. python异步回调函数的实现

    #coding:utf-8 from socket import * import time #简单的服务器程序 监听用户连接,接收用户发来的信息,并返回反馈 def main(): HOST = & ...

随机推荐

  1. 用StabilityMatrix一键安装Stable Diffusion

    Stable Diffusion是2022年发布的深度学习文字到图像生成模型,它既能免费使用,又能部署在本地端,又有非常多的模型可以直接套用,在使用体验上比Midjourney和DALL-E更加强大. ...

  2. Spring注解之参数校验@Validated和@Valid

    @Validated和@Valid的区别 Spring Validation验证框架对参数的验证机制提供了@Validated(Spring's JSR-303 规范,是标准 JSR-303 的一个变 ...

  3. ComfyUI进阶:Comfyroll插件 (一)

    ComfyUI进阶:Comfyroll插件 (一) 前言: 学习ComfyUI是一场持久战,而Comfyroll Studio 是一款功能强大的自定义节点集合,专为 ComfyUI 用户打造,旨在提供 ...

  4. oeasy教您玩转vim - 21 - 状态横条

    状态横条 回忆上节课内容 我们上次研究了标尺 标尺 开启 se ru 关闭 se noru 行号 开启 se nu 关闭 se nonu 命令位置 开启 se showcmd 关闭 se noshow ...

  5. 矩阵的奇异值分解(SVD)及其应用

    奇异值分解(Singular Value Decomposition, SVD)是矩阵的一种分解方法,与特征值分解不同,它可以应用于长方矩阵中,并将其分解成三个特殊矩阵的乘积.此外SVD分解还具有许多 ...

  6. Jmeter函数助手2-Random

    Random函数用于获取随机范围内的正整数或负整数. 一个范围内的最小值:必填,且必须填入整数(正负数都可以) 一个范围内允许的最大值:必填,且必须填入整数(正负数都可以).最大值需大于最小值如[-6 ...

  7. 06 定时器和PWM(1)

    前言 前面介绍了一下外部中断,这一节主要介绍一下内部定时器和PWM,这两个知识还是比较重要的. 一.定时器 1.什么是定时器 定时器其实和计数器一样,我们通过设置一个值,当计数器运行一个计数寄存器向上 ...

  8. vue加载三维模型

    创建项目 我使用的是Vue CLI3,具体创建不再赘述,网上教程很多 下载SuperMap iClient3D for WebGL产品包 链接:http://support.supermap.com. ...

  9. 【Layui】15 日期时间选择器 Laydate

    文档地址: https://www.layui.com/demo/laydate.html [基本案例] 基本日期与国际日期 <fieldset class="layui-elem-f ...

  10. 人工智能(AI)未来之方向:努力培养人才、科研创新!

    地址: https://baijiahao.baidu.com/s?id=1801824912676717630&wfr=spider&for=pc 人工智能(AI)未来之方向 1. ...