记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) ...
随机推荐
- sql里面插入语句insert后面的values关键字可省略
插入到表名(列值)后跟一个查询语句的话就代表值,简单的说就是后面select select出来的值就是要插入的值,即 insert into tb(字段名一,字段名二)select 字段名一,字段名 ...
- error LNK2005:"private:__thiscall编译错误
对于这种编译错误,网上给出了很多解决办法,大部分都是忽略特定库,或者改变多线程调试DLL,但是均没有效果. 这里记录下自己的解决方法,首先按照下图,取消从父级或项目默认设置继承,避免与其他库中的定义冲 ...
- python3 新特性
1.格式化字符串f-string user = "Mike" log_message = f'User{user} has logged in' 2.路径管理库Pathlib 3. ...
- JavaSE_02_Thread类01
1.1 并发与并行 并发:指两个或多个事件在同一个时间段内发生. 这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运 ...
- matlab中disp函数的简单用法
输出数组类型的数据,也可以把string类型的数据看做数组输出 输出数字 >> num = ; >> disp(num) 输出字符串 >> disp('this i ...
- hbase 利用rowkey设计进行多条件查询
摘要 本文主要内容是通过合理Hbase 行键(rowkey)设计实现快速的多条件查询,所采用的方法将所有要用于查询中的列经过一些处理后存储在rowkey中,查询时通过rowkey进行查询,提高rowk ...
- PHP jpgraph的一点小提示和方法
PHP默认是不启用GD库的,因为需要在php.ini的配置文件中将extension=php_gd2.dll注释打开.打开后你就可以画一些你想画的各种奇葩图案了.什么?不会画?那回去学基础! 今天看了 ...
- MySQL系列(九)--InnoDB索引原理
InnoDB在MySQL5.6版本后作为默认存储引擎,也是我们大部分场景要使用的,而InnoDB索引通过B+树实现,叫做B-tree索引.我们默认创建的 索引就是B-tree索引,所以理解B-tree ...
- Django项目:CRM(客户关系管理系统)--34--26PerfectCRM实现King_admin自定义排序
ordering = ['-qq'] #自定义排序,默认'-id' #base_admin.py # ————————24PerfectCRM实现King_admin自定义操作数据———————— f ...
- 通过gevent实现单线程下的多socket并发
#通过gevent实现单线程下的多socket并发 服务器 #server side import sys import socket import time import gevent from g ...