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

最新文章

  1. *HDU 1007 计算几何
  2. android内存分析:heap Snapshot的使用
  3. 别老扯什么Hadoop了,你的数据根本不够大
  4. 几种网络加载的过渡(更新MaterialProgressBar)
  5. WPF中RadioButton的分组
  6. AndroidAnnotations框架配置
  7. zookeeper入门与实践
  8. springmvc05-Spring+Springmvc+Hibernate实现简单的用户管理系统
  9. DLL文件修复
  10. java连接Mysql8
  11. EF部分字段更新,忽略为null字段
  12. jdk下载地址
  13. Mongodb系列- CRUD操作介绍
  14. 【linux】crontab失效
  15. 关系操作符 &lt; &gt; = == &lt;= &gt;= !=
  16. Asp.NET调用有道翻译API
  17. React中Props 和 State用法
  18. 【LeetCode每天一题】Search in Rotated Sorted Array(在旋转数组中搜索)
  19. uniGUI试用笔记(三)
  20. AndroidStudio3.0以上版本的坑

热门文章

  1. JZOJ5883【NOIP2018模拟A组9.25】到不了——动态LCA裸题
  2. php冒泡算法
  3. Django项目:CMDB(服务器硬件资产自动采集系统)--03--03CMDB信息安全API接口交互认证
  4. 尝试一下LLJ大佬的理论AC大法
  5. 19-10-23-L-Mor
  6. 第一个SpringBoot插件-捕获请求并且支持重新发起
  7. 构建支持中文字体的moviepy镜像
  8. Linux 7.X 解锁用户账号
  9. HBase的一些关于CRUD方法
  10. 移动端H5适配流程