zoukankan      html  css  js  c++  java
  • 记UWP开发——多线程操作/并发操作中的坑

    一切都要从新版风车动漫UWP的图片缓存功能说起。

    起因便是风车动漫官网的番剧更新都很慢,所以图片更新也非常慢。在开发新版的过程中,我很简单就想到了图片多次重复下载导致的资源浪费问题。

    所以我给app加了一个缓存机制:

    创建一个用户控件CoverView,将首页GridView.ItemTemplate里的Image全部换成CoverView

    CoverView一旦接到ImageUrl的修改,就会自动向后台的PictureHelper申请指定Url的图片

    PictureHelper会先判断本地是否有这个Url的图片,没有的话从风车动漫官网下载一份,保存到本地,然后返回给CoverView

    关键就是PictureHelper的GetImageAsync方法

    本地缓存图片的代码片段:

        //缓存文件名以MD5的形式保存在本地
        string name = StringHelper.MD5Encrypt16(Url);
    
    
        if (imageFolder == null)
            imageFolder = await cacheFolder.CreateFolderAsync("imagecache", CreationCollisionOption.OpenIfExists);
        StorageFile file;
        IRandomAccessStream stream = null;
        if (File.Exists(imageFolder.Path + "\" + name))
        {
            file = await imageFolder.GetFileAsync(name);
            stream = await file.OpenReadAsync();
        }
    
        //文件不存在or文件为空,通过http下载
        if (stream == null || stream.Size == 0)
        {
            file = await imageFolder.CreateFileAsync(name, CreationCollisionOption.ReplaceExisting);
            stream = await file.OpenAsync(FileAccessMode.ReadWrite);
            IBuffer buffer = await HttpHelper.GetBufferAsync(Url);
            await stream.WriteAsync(buffer);
        }
        
        //...

    嗯...一切都看似很美好....

    但是运行之后,发现了一个很严重的偶发Exception

    查阅google良久后,得知了发生这个问题的原因:

    主页GridView一次性加载了几十个Item后,几十个Item中的CoverView同时调用了PictureHelper的GetImageAsync方法

    几十个PictureHelper的GetImageAsync方法又同时访问缓存文件夹,导致了非常严重的IO锁死问题,进而引发了大量的UnauthorizedAccessException

    有=又查阅了许久之后,终于找到了解决方法:

    SemaphoreSlim异步锁

    使用方法如下:

            private static SemaphoreSlim asyncLock = new SemaphoreSlim(1);//1:信号容量,即最多几个异步线程一起执行,保守起见设为1
    
            public async static Task<WriteableBitmap> GetImageAsync(string Url)
            {
                if (Url == null)
                    return null;
                try
                {
                    await asyncLock.WaitAsync();
    
                    //缓存文件名以MD5的形式保存在本地
                    string name = StringHelper.MD5Encrypt16(Url);
    
    
                    if (imageFolder == null)
                        imageFolder = await cacheFolder.CreateFolderAsync("imagecache", CreationCollisionOption.OpenIfExists);
                    StorageFile file;
                    IRandomAccessStream stream = null;
                    if (File.Exists(imageFolder.Path + "\" + name))
                    {
                        file = await imageFolder.GetFileAsync(name);
                        stream = await file.OpenReadAsync();
                    }
    
                    //文件不存在or文件为空,通过http下载
                    if (stream == null || stream.Size == 0)
                    {
                        file = await imageFolder.CreateFileAsync(name, CreationCollisionOption.ReplaceExisting);
                        stream = await file.OpenAsync(FileAccessMode.ReadWrite);
                        IBuffer buffer = await HttpHelper.GetBufferAsync(Url);
                        await stream.WriteAsync(buffer);
                    }
    
                    //...
    
                }
                catch(Exception error)
                {
                    Debug.WriteLine("Cache image error:" + error.Message);
                    return null;
                }
                finally
                {
                    asyncLock.Release();
                }
            }
        

    成功解决了并发访问IO的问题

    但是在接下来的Stream转WriteableBitmap的过程中,问题又来了....

    这个问题比较好解决

                    BitmapDecoder bitmapDecoder = await BitmapDecoder.CreateAsync(stream);
                    WriteableBitmap bitmap = null;
                    await Window.Current.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async delegate
                    {
                        bitmap = new WriteableBitmap((int)bitmapDecoder.PixelWidth, (int)bitmapDecoder.PixelHeight);
                        stream.Seek(0);
                        await bitmap.SetSourceAsync(stream);
                    });
                    stream.Dispose();
                    return bitmap;

    使用UI线程来跑就ok了

    然后!问题又来了

    WriteableBitmap到被return为止,都很正常

    但是到接下来,我在CoverView里做其他一些bitmap的操作时,出现了下面这个问题

    又找了好久,最后回到bitmap的PixelBuffer一看,擦,全是空的?

    虽然bitmap成功的new了出来,PixelHeight/Width啥的都有了,当时UI线程中的SetSourceAsync压根没执行完,所以出现了内存保护的神奇问题

    明明await了啊?

    最后使用这样一个奇技淫巧,最终成功完成

                    BitmapDecoder bitmapDecoder = await BitmapDecoder.CreateAsync(stream);
                    WriteableBitmap bitmap = null;
                    TaskCompletionSource<bool> task = new TaskCompletionSource<bool>();
                    await Window.Current.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async delegate
                    {
                        bitmap = new WriteableBitmap((int)bitmapDecoder.PixelWidth, (int)bitmapDecoder.PixelHeight);
                        stream.Seek(0);
                        await bitmap.SetSourceAsync(stream);
                        task.SetResult(true);
                    });
                    await task.Task;

    关于TaskCompletionSource,请参阅

    https://www.cnblogs.com/loyieking/p/9209476.html

    最后总算是完成了....

            public async static Task<WriteableBitmap> GetImageAsync(string Url)
            {
                if (Url == null)
                    return null;
                try
                {
                    await asyncLock.WaitAsync();
    
                    //缓存文件名以MD5的形式保存在本地
                    string name = StringHelper.MD5Encrypt16(Url);
    
    
                    if (imageFolder == null)
                        imageFolder = await cacheFolder.CreateFolderAsync("imagecache", CreationCollisionOption.OpenIfExists);
                    StorageFile file;
                    IRandomAccessStream stream = null;
                    if (File.Exists(imageFolder.Path + "\" + name))
                    {
                        file = await imageFolder.GetFileAsync(name);
                        stream = await file.OpenReadAsync();
                    }
    
                    //文件不存在or文件为空,通过http下载
                    if (stream == null || stream.Size == 0)
                    {
                        file = await imageFolder.CreateFileAsync(name, CreationCollisionOption.ReplaceExisting);
                        stream = await file.OpenAsync(FileAccessMode.ReadWrite);
                        IBuffer buffer = await HttpHelper.GetBufferAsync(Url);
                        await stream.WriteAsync(buffer);
                    }
    
                    BitmapDecoder bitmapDecoder = await BitmapDecoder.CreateAsync(stream);
                    WriteableBitmap bitmap = null;
                    TaskCompletionSource<bool> task = new TaskCompletionSource<bool>();
                    await Window.Current.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async delegate
                    {
                        bitmap = new WriteableBitmap((int)bitmapDecoder.PixelWidth, (int)bitmapDecoder.PixelHeight);
                        stream.Seek(0);
                        await bitmap.SetSourceAsync(stream);
                        task.SetResult(true);
                    });
                    await task.Task;
                    stream.Dispose();
                    return bitmap;
    
                }
                catch(Exception error)
                {
                    Debug.WriteLine("Cache image error:" + error.Message);
                    return null;
                }
                finally
                {
                    asyncLock.Release();
                }
            }
  • 相关阅读:
    php无刷新上传图片和文件
    wamp(win1064位家庭版+apache2.4.20+php5.5.37+mysql5.5.50)环境搭建
    linux下常见解压缩命令
    nginx虚拟主机配置小结
    Nginx下配置ThinkPHP的URL Rewrite模式和pathinfo模式支持
    iframe 高度自适应/以及在ie8中空白问题
    小程序-调用公共js对象方法/ app.js
    小程序-tabbar相关样式设置
    微信小程序连接无法跳转/ can not navigate to tabBar page错误
    微信小程序-tabBar无法显示问题解析
  • 原文地址:https://www.cnblogs.com/loyieking/p/9506036.html
Copyright © 2011-2022 走看看