addImageAsync异步加载
未响应回调前调用unbindImageAsync撤销消息回调
void TextureCache::unbindImageAsync(const std::string& filename)
{
_imageInfoMutex.lock();
if (_imageInfoQueue && !_imageInfoQueue->empty())
{
std::string fullpath = FileUtils::getInstance()->fullPathForFilename(filename);
auto found = std::find_if(_imageInfoQueue->begin(), _imageInfoQueue->end(), [&fullpath](ImageInfo* ptr)->bool{ return ptr->asyncStruct->filename == fullpath; });
if (found != _imageInfoQueue->end())
{
(*found)->asyncStruct->callback = nullptr;
}
}
_imageInfoMutex.unlock();
}
可是在极端状况下,调用 addImageAsync后立刻调用unbindImageAsync
此时loadImage线程还未将ImageInfo加入_imageInfoQueue
而是在unbindImageAsync以后添加,致使消息回调解绑失败php
没法调用(*found)->asyncStruct->callback = nullptr;
从而在异步加载完成后调用callback时候出现问题
html
loadImage()线程
同时操做两个链表
_asyncStructQueue
_imageInfoQueue
请求从_asyncStructQueue弹出后生成ImageInfo添加到_imageInfoQueue
void TextureCache::loadImage()
{
_asyncStructQueueMutex.lock();
_asyncStructQueue
_asyncStructQueueMutex.unlock();
.....临界点,执行unbindImageAsync
_imageInfoMutex.lock();
_imageInfoQueue->push_back(imageInfo);
_imageInfoMutex.unlock();
}
此处的Bug是两个链表是分别加锁的
请求从_asyncStructQueue弹出后,未即时插入_imageInfoQueue
在临界点又调用了unbindImageAsync撤销异步加载响应
跟昨天提到的Bug相似,一样会致使撤销失败
在回调时引发异常 安全
=================================================================异步
如下为转帖async
就以往的经验,异步加载图片是一个复杂的工做,每每容易出现bug。
那么,cocos2d-x提供的这个异步加载功能是否可靠?百度了一下,没发现什么重要的信息,因而本身分析之。
照cocos2d-x自身的注释来看,这个addImageAsync函数是从0.8版本就有了,而如今是3.1版本,怎么也该稳定了吧?惋惜的是,里面的陷阱并很多。
陷阱1:_textures未加锁
在异步加载时,cocos2d-x主要用了两个队列,即_asyncStructQueue和_imageInfoQueue,在操做这两个队列的时候也都很当心的加锁了。可是对_textures的访问则没有加锁。所以,若是先用addImageAsync进行异步加载图片A,再用addImage同步加载图片B,则有概率致使_textures这个对象被损坏,进而致使程序不稳定。
修改:因为涉及到_textures的地方不少,逐一加锁实在麻烦,因此干脆不加锁,转为让异步线程不要访问_textures。大不了就是同一张图片被屡次加载,浪费一些运算量罢了。上层代码当心控制的话,是不会真的有图片被重复加载的。
陷阱2:判断逻辑错误
在cocos2d-x 3.1.1版本中,异步加载的代码中有一句判断:if(imageInfo->asyncStruct->filename.compare(asyncStruct->filename)),这是有问题的。
做者的本意多是想,若是队列中有多个请求都是加载同一幅图片,那么其实只须要加载一次便可。惋惜这个判断写反了,下文又有一处判断写反,致使不知所云。
这个问题在cocos2d-x 3.2版本已经修复了。
P.S. 字符串比较,仍是用==、!=这样的操做符比较好,可读性和运行性能都要优于compare函数。
修改:cocos官方已经修正。不过其实跟陷阱1相似,没必要判断,大不了就是同一张图片被屡次加载,浪费一些运算量罢了。
陷阱3:insert失败致使内存泄漏
在异步加载完毕以后,主线程有一句:_textures.insert( std::make_pair(filename, texture) );。
因为陷阱一、陷阱2中,咱们并无进行完全的检查,因此有概率出现重复加载的情形。(实际上,除非全程加锁,不然很难完全避免重复加载。然而,全程加锁的话,异步加载也就没有意义了)。当出现重复加载同一张图片时,这里的insert就会失败。因而,texture不会有被销毁的机会,因而形成内存泄漏。
陷阱4:建立线程时,须要的变量还没有初始化完毕
建立异步加载的线程时,原始代码是先建立线程,再设置_needQuit。
正常应该是先设置_needQuit为false(初始化值为true!),再建立线程。不然理论上有可能线程刚建立完毕就当即结束。
疑似陷阱
异步加载线程和主线程,都调用了Image::initWithImageFileThreadSafe,这个函数看名字彷佛是线程安全的,实际上它调用了FileUtils::fullPathForFilename。这个函数除非参数是绝对路径,不然就会对一个名为_fullPathCache的哈希表进行读写,若不加锁就会出错。幸亏在异步加载线程中,传入给FileUtils::fullPathForFilename的参数已是绝对路径,因此没有上述的问题。 函数