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 = & ...
随机推荐
- 开源一个常用的树目录和下拉树js组件
我写的一个常用的树目录组件,需要jquery和font-awesome支持,对于想使用自定义图标的可以重定义 fa样式即可,或者直接更换源码的样式名称. 下载地址:https://github.com ...
- Java开发框架演变过程
JavaWeb开发简史 Java框架创始人 Java框架说明 Spring: 把应用程序中的bean统一交给Spring进行管理控制,简化了我们的代码操作,和降低了代码的耦合度,Spring框架基本上 ...
- 六.黑马程序员-eclipse的使用和快捷键
1.Eclipse的概述 A: 是一个集成开发工具,专门针对java的 B: Eclipse 免费的 开源 C: MyEclipse 收费的 具体良好的插件扩展功能,针对插件收费2.Eclipse的使 ...
- 网易传媒基于 Arctic 的低成本准实时计算实践
网易传媒大数据实际业务中,存在着大量的准实时计算需求场景,业务方对于数据的实效性要求一般是分钟级:这种场景下,用传统的离线数仓方案不能满足用户在实效性方面的要求,而使用全链路的实时计算方案又会带来较高 ...
- ASP.NET MVC / WebAPI 路由机制详解
从MVC到WebApi,路由机制一直都在其中扮演着重要的角色. 它可以很简单:如果你只需要会用一些简单的路由,如/Home/Index那么你只需要配置一个默认路由就能搞定. 它可以很神秘:你的url可 ...
- Prometheus 使用Python推送指标数据到Pushgateway
使用Python推送指标数据到Pushgateway 需求描述 实践环境 Python 3.6.5 Django 3.0.6 prometheus-client 0.11.0 代码实现 !/usr/b ...
- Jmeter参数化1-随机数设置
背景:当新增接口的某个字段是唯一性,每次调用该新增接口都会需要单独传入这个字段,麻烦且繁琐. 解决:jmeter设置随机数参数,然后接口调用该参数就达到了自动性不再需要人工传入不同的值.方便调用接口, ...
- argparse学习笔记
argparse是 Python 的一个内置模块,用于编写用户友好的命令行接口.使用 argparse,你可以很容易地为 Python 脚本添加参数解析功能,使得脚本可以接受命令行选项和参数.学起来也 ...
- mysql密码的初始化,修改与重置
目录 mysql密码的初始化,修改与重置 郑重说明: 初始化密码(第一次使用前要初始化密码) 查看密码(已登录状态) 修改密码(已知原密码) 忘记密码(密码找回) 诺mysql装在Windows 诺m ...
- 【Java】暂存逻辑
需求说明: 需求是填写一个表单时暂时保存输入项,不提交表单 回来再次填写时可以恢复或者放弃,或者更改内容继续暂存 放两张UI图,一个移动端,一个手机端: 逻辑分析: 存储方式有这么几种,Cookie存 ...