一切都要从新版风车动漫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 == )
{
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

        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 == )
{
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();
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();
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 == )
{
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();
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();
}
}

记UWP开发——多线程操作/并发操作中的坑的更多相关文章

  1. Android开发 ---多线程操作:Handler对象,消息队列,异步任务下载

    效果图: 1.activity_main.xml 描述:定义了六个按钮 <?xml version="1.0" encoding="utf-8"?> ...

  2. 更高效地提高redis client多线程操作的并发吞吐设计

    Redis是一个非常高效的基于内存的NOSQL数据库,它提供非常高效的数据读写效能.在实际应用中往往是带宽和CLIENT库读写损耗过高导致无法更好地发挥出Redis更出色的能力.下面结合一些redis ...

  3. 编写Java程序,实现多线程操作同一个实例变量的操作会引发多线程并发的安全问题。

    查看本章节 查看作业目录 需求说明: 多线程操作同一个实例变量的操作会引发多线程并发的安全问题.现有 3 个线程代表 3 只猴子,对类中的一个整型变量 count(代表花的总数,共 20 朵花)进行操 ...

  4. C#多线程操作界面控件的解决方案(转)

    C#中利用委托实现多线程跨线程操作 - 张小鱼 2010-10-22 08:38 在使用VS2005的时候,如果你从非创建这个控件的线程中访问这个控件或者操作这个控件的话就会抛出这个异常.这是微软为了 ...

  5. [书籍]用UWP复习《C#并发编程经典实例》

    1. 简介 C#并发编程经典实例 是一本关于使用C#进行并发编程的入门参考书,使用"问题-解决方案-讨论"的模式讲解了以下这些概念: 面向异步编程的async和await 使用TP ...

  6. iOS开发多线程篇—多线程简单介绍

    iOS开发多线程篇—多线程简单介绍 一.进程和线程 1.什么是进程 进程是指在系统中正在运行的一个应用程序 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内 比如同时打开QQ.Xcod ...

  7. UWP开发入门(二十一)——保持Ui线程处于响应状态

    GUI的程序有时候会因为等待一个耗时操作完成,导致界面卡死.本篇我们就UWP开发中可能遇到的情况,来讨论如何优化处理. 假设当前存在点击按钮跳转页面的操作,通过按钮打开的新页面,在初始化过程中存在一些 ...

  8. swift开发多线程篇 - 多线程基础

    swift开发多线程篇 - 多线程基础 iOS 的三种多线程技术 (1)NSThread  使用NSThread对象建立一个线程非常方便 但是!要使用NSThread管理多个线程非常困难,不推荐使用 ...

  9. iOS 开发多线程篇—GCD的常见用法

    iOS开发多线程篇—GCD的常见用法 一.延迟执行 1.介绍 iOS常见的延时执行有2种方式 (1)调用NSObject的方法 [self performSelector:@selector(run) ...

随机推荐

  1. MapReduce 图解流程

    Anatomy of a MapReduce Job In MapReduce, a YARN application is called a Job. The implementation of t ...

  2. [51nod-1364]最大字典序排列

    [51nod-1364]最大字典序排列 Online Judge:51nod-1364 Label:线段树,树状数组,二分 题目描述 题解: 根据题意很容易想到60%数据的\(O(N^2logN)\) ...

  3. 关于CoCreateInstance的0x800401f0问题

    hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&g_pGr ...

  4. HZOI 可怜与超市 树形dp

    学长留的题,质量还是灰常高的. 而且我树规本身较弱,一道也不想放下 题目链接:https://www.cnblogs.com/Juve/articles/11203824.html 题解:这道题我们可 ...

  5. Tensorflow技巧

    1.尽量控制图片大小在1024以内,不然显存会爆炸. 2.尽量使用多GPU并行工作,训练下降速度快. 3.当需要被检测的单张图片里物体太多时,记得修改Region_proposals的个数 4.测试的 ...

  6. openldap 2.4 centos7 常用配置

    新版的openldap弃用了sldap.conf配置文件,引入一种动态配置,所以尽量不要直接修改配文件 如果直接修改了配置文件可以用slaptest -u命令检查 1.安装openldap,可能需要e ...

  7. php实现的支持断点续传的文件下载类

    通常来说,php支持断点续传,主要依靠HTTP协议中 header HTTP_RANGE实现. HTTP断点续传原理: Http头 Range.Content-Range()HTTP头中一般断点下载时 ...

  8. 『Power AI by AI』 PAI-AutoML2.0重磅发布

    PAI-AutoML调参服务是通过算法的方式解放用户调节算法参数的工作.自2018年8月发布PAI-AutoML1.0版本以来,该功能已经帮助众多PAI的中小企业用户提升了模型的准确性,得到了不错的反 ...

  9. Django项目:CRM(客户关系管理系统)--50--41PerfectCRM实现全局账号密码修改

    # gbacc_urls.py # ————————38PerfectCRM实现全局账号登录注销———————— from django.conf.urls import url from gbacc ...

  10. 作业-[luogu4396][AHOI2013]-莫队

    <题面> 卡常终究比不上算法的优化…… 这是莫队的有点小坑的题, 首先不一定能想到,想到不一定打对,打对不一定打好. 首先你会发现,这个题的时限是很长的- $n$和$m$也是很大的. 于是 ...