码迷,mamicode.com
首页 > 其他好文 > 详细

cocos2dx-3 addImageAsync陷阱

时间:2015-07-19 23:56:46      阅读:1675      评论:0      收藏:0      [点我收藏+]

标签:

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之后添加,导致消息回调解绑失败

无法调用(*found)->asyncStruct->callback = nullptr;
从而在异步加载完成后调用callback时候出现问题

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类似,同样会导致撤销失败
在回调时引起异常技术分享  


=================================================================

以下为转帖

就以往的经验,异步加载图片是一个复杂的工作,往往容易出现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) );。 
        由于陷阱1、陷阱2中,我们并没有进行彻底的检查,所以有几率出现重复加载的情形。(实际上,除非全程加锁,否则很难彻底避免重复加载。然而,全程加锁的话,异步加载也就没有意义了)。当出现重复加载同一张图片时,这里的insert就会失败。于是,texture不会有被销毁的机会,于是造成内存泄漏。 
          
        陷阱4:创建线程时,需要的变量尚未初始化完毕 
        创建异步加载的线程时,原始代码是先创建线程,再设置_needQuit。 
        正常应该是先设置_needQuit为false(初始化值为true!),再创建线程。否则理论上有可能线程刚创建完毕就立即结束。 
          
        疑似陷阱 
        异步加载线程和主线程,都调用了Image::initWithImageFileThreadSafe,这个函数看名字似乎是线程安全的,实际上它调用了FileUtils::fullPathForFilename。这个函数除非参数是绝对路径,否则就会对一个名为_fullPathCache的哈希表进行读写,若不加锁就会出错。幸好在异步加载线程中,传入给FileUtils::fullPathForFilename的参数已经是绝对路径,所以没有上述的问题。 


http://www.cocoachina.com/bbs/read.php?tid-312395.html

http://www.58player.com/blog-2479-108013.html



cocos2dx-3 addImageAsync陷阱

标签:

原文地址:http://my.oschina.net/robslove/blog/480514

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!