记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 == )
{
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开发——多线程操作/并发操作中的坑的更多相关文章
- Android开发 ---多线程操作:Handler对象,消息队列,异步任务下载
效果图: 1.activity_main.xml 描述:定义了六个按钮 <?xml version="1.0" encoding="utf-8"?> ...
- 更高效地提高redis client多线程操作的并发吞吐设计
Redis是一个非常高效的基于内存的NOSQL数据库,它提供非常高效的数据读写效能.在实际应用中往往是带宽和CLIENT库读写损耗过高导致无法更好地发挥出Redis更出色的能力.下面结合一些redis ...
- 编写Java程序,实现多线程操作同一个实例变量的操作会引发多线程并发的安全问题。
查看本章节 查看作业目录 需求说明: 多线程操作同一个实例变量的操作会引发多线程并发的安全问题.现有 3 个线程代表 3 只猴子,对类中的一个整型变量 count(代表花的总数,共 20 朵花)进行操 ...
- C#多线程操作界面控件的解决方案(转)
C#中利用委托实现多线程跨线程操作 - 张小鱼 2010-10-22 08:38 在使用VS2005的时候,如果你从非创建这个控件的线程中访问这个控件或者操作这个控件的话就会抛出这个异常.这是微软为了 ...
- [书籍]用UWP复习《C#并发编程经典实例》
1. 简介 C#并发编程经典实例 是一本关于使用C#进行并发编程的入门参考书,使用"问题-解决方案-讨论"的模式讲解了以下这些概念: 面向异步编程的async和await 使用TP ...
- iOS开发多线程篇—多线程简单介绍
iOS开发多线程篇—多线程简单介绍 一.进程和线程 1.什么是进程 进程是指在系统中正在运行的一个应用程序 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内 比如同时打开QQ.Xcod ...
- UWP开发入门(二十一)——保持Ui线程处于响应状态
GUI的程序有时候会因为等待一个耗时操作完成,导致界面卡死.本篇我们就UWP开发中可能遇到的情况,来讨论如何优化处理. 假设当前存在点击按钮跳转页面的操作,通过按钮打开的新页面,在初始化过程中存在一些 ...
- swift开发多线程篇 - 多线程基础
swift开发多线程篇 - 多线程基础 iOS 的三种多线程技术 (1)NSThread 使用NSThread对象建立一个线程非常方便 但是!要使用NSThread管理多个线程非常困难,不推荐使用 ...
- iOS 开发多线程篇—GCD的常见用法
iOS开发多线程篇—GCD的常见用法 一.延迟执行 1.介绍 iOS常见的延时执行有2种方式 (1)调用NSObject的方法 [self performSelector:@selector(run) ...
随机推荐
- Leetcode 242.有效的字母异位词(Python3)
题目: 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词. 示例 1: 输入: s = "anagram", t = "nagaram& ...
- [SHOI2007] 书柜的尺寸 思维题+Dp+空间优化
Online Judge:Luogu-P2160 Label:思维题,Dp,空间优化 题面: 题目描述 给\(N\)本书,每本书有高度\(Hi\),厚度\(Ti\).要摆在一个三层的书架上. 书架的宽 ...
- vue.js_12_vue的watch和computed
1.watch用来监测指定Vue实例上的数据变动. watch主要用于监控vue实例的变化,它监控的变量当然必须在data里面声明才可以,它可以监控一个变量,也可以是一个对象. 1.>使用wat ...
- Nodejs Express目录结构
原文 http://www.leixuesong.cn/1508 Express是一个基于Node.js平台的极简.灵活的web应用开发框架.在前面我们已经详细介绍了Express的安装,下面详细讲 ...
- 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 ...
- Go语言cookie的基本操作
cookie数据结构介绍 cookie数据结构介绍 a. Expires,cookie过期时间,使用绝对时间.比如2018/10/10 10:10:10b. MaxAge,cookie过期时间,使用相 ...
- Spring MVC(二)--Spring MVC登陆实例
本文通过一个简单的登陆实例实现Spring MVC的流程,同时整合 MyBatis使用,流程是这样的: 1.访问一个URL进入登陆界面 2.输入正确的用户名和密码,成功则进入index页面,否则留在登 ...
- csdn vip文章:使用matlab模拟镜头失真
原文地址 https://blog.csdn.net/lircsszz/article/details/80249017 最近在研究图像校正,现将镜头失真中常见的径向畸变(radial distort ...
- hashMap 源码解读理解实现原理和hash冲突
hashMap 怎么说呢. 我的理解是 外表是一个set 数组,无序不重复 . 每个set元素是一个bean ,存着一对key value 看看代码吧 package test; import jav ...
- C#获取C# DLL中的指定接口的所有实现实例 - qq_19759475的博客 - CSDN博客
原文:C#获取C# DLL中的指定接口的所有实现实例 - qq_19759475的博客 - CSDN博客 public static List<T> CreateTarInterface& ...