一切都要从新版风车动漫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. python3.6.4安装错误0x80072efd

    是因为翻不了墙,请求URL错误,所以报错那么去掉Download debug sysbom选项

  2. 2019-9-2-C#判断文件属于文本或二进制

    title author date CreateTime categories C#判断文件属于文本或二进制 lindexi 2019-09-02 12:57:37 +0800 2018-2-13 1 ...

  3. 验证sll证书与密钥

    $crtParse = openssl_x509_parse($ssl_content); //$ssl_content 证书内容 .crt文件内容 if (!$crtParse) { return ...

  4. Python-爬虫实战 简单爬取豆瓣top250电影保存到本地

    爬虫原理 发送数据 获取数据 解析数据 保存数据 requests请求库 res = requests.get(url="目标网站地址") 获取二进制流方法:res.content ...

  5. xshell上传文件到linux

    z,sz是Linux/Unix同Windows进行ZModem文件传输的命令行工具. 优点就是不用再开一个sftp工具登录上去上传下载文件. sz:将选定的文件发送(send)到本地机器 rz:运行该 ...

  6. Git - fatal error : Agent admitted failure to sign using the key

    提交时出现如下问题: git push -u origin master Agent admitted failure to sign using the key. Permission denied ...

  7. Java学习笔记 - 类方法与代码块的执行顺序

    类的初始化顺序 使用一个简单的父子类例子来做示范,代码执行顺序在代码后有标注. class Parent { public static String p_StaticField = "父类 ...

  8. tensorflow高效地推导pb模型,完整代码

    from matplotlib import pyplot as plt import numpy as np import os import six.moves.urllib as urllib ...

  9. Leetcode153. Find Minimum in Rotated Sorted Array寻找旋转排序数组中最小值

    假设按照升序排序的数组在预先未知的某个点上进行了旋转. ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] ). 请找出其中最小的元素. 你可以假设数组中不存在重 ...

  10. git学习记录——分支管理和多人协作

    在公司里难免会出现多个人一起工作,这就需要构建多个分支派发给多个人去干活 这就产生一个需求,分支管理 分支的创建,合并和删除 其他版本控制系统如SVN等都有分支管理,但是用过之后你会发现,这些版本控制 ...