项目有这样的需求,
要求窗口加载一揽子图片,为了不让UI阻塞太久,采用异步读取后绑定显示的方案.
图片的下载应该采用并发的过程(等待网络响应会很耗时,一张一张的下载,等待时间太长)
图片的下载不能占用过多的线程数,应有个阀值(图片不是核心业务,不能占用那么多资源)
在图片加载的过程中,如果用户有操作,比如窗口跳转,则未加载完成的图片加载的过程应取消(为了替用户节省流量).
需求就是这么多了,如何实现呢?
思路是这样的,由于需要异步,且需要等待,首先想到使用队列,先让队列排列起来,再定量迭代读取.
因为要涉及异步的取消,想到了用WebClient对象的异步功能, 当然,所以发起异步请求之后的对象我都需要记录,
所以还需要一个list容器.
外部接口是两个参数,url,图片的网址,一个回调,定义了图片下载完成后的操作.
内部的核心流程,
1.将一个图片任务从队列中取出,
2.异步发生此请求,
3.将发起请求的对象放进容器,以备撤销时使用.
撤销的核心流程是.
1.让处理线程停止
2.取消队列中的任务,
3.让等待响应的任务取消.
需要用到以下命名空间:
using
System;
using
System.Collections.Generic;
using
System.Net;
using
System.Windows;
using
Microsoft.Phone.Controls;
using
System.Windows.Media.Imaging;
using
System.IO;
using
System.ComponentModel;
using
System.Threading;
先声明两个回调
public
delegate
void
GetDataStreamCallback(Stream stream);
public
delegate
void
GetPicCallback(BitmapSource bimage);
第一个是从网络读取流的回调,第二个是生成了图片源之后的回调.
定义了一个接口:
///
<summary>
///
可以撤销的操作
///
</summary>
interface
IRevocable
{
void
RevokeAsync();
event
Action ProcessCompleted;
}
一个方法,是用来撤销操作的,
一个事件,是当异步完成时触发的.(不管是正常完成还是撤销,都视为操作完成)
做一个类继承该接口,用来获取网络数据
public
class
HttpResourceGet : IRevocable
{
public
event
GetDataStreamCallback OnDataStreamGenerated;
public
event
Action ProcessCompleted;
WebClient m_client;
public
HttpResourceGet()
{
m_client
=
new
WebClient();
m_client.OpenReadCompleted
+=
((send, ev)
=>
{
do
{
if
(ev.Error
!=
null
||
ev.Cancelled)
{
break
;
}
if
(OnDataStreamGenerated
!=
null
)
{
OnDataStreamGenerated(ev.Result);
ev.Result.Close();
}
}
while
(
false
);
if
(ProcessCompleted
!=
null
)
{
ProcessCompleted();
}
});
}
public
void
BeginGetData(
string
url)
{
m_client.OpenReadAsync(
new
Uri(url));
}
public
void
RevokeAsync()
{
m_client.CancelAsync();
}
}
再做一个类,把网络数据包装为图片源
public
class
HttpPicGet : IRevocable
{
public
event
GetPicCallback OnImageLoadCompleted;
public
event
Action ProcessCompleted;
HttpResourceGet m_httpGet;
public
HttpPicGet()
{
m_httpGet
=
new
HttpResourceGet();
m_httpGet.OnDataStreamGenerated
+=
(stream
=>
{
BitmapSource bi
=
new
BitmapImage();
bi.SetSource(stream);
if
(OnImageLoadCompleted
!=
null
)
{
OnImageLoadCompleted(bi);
}
});
m_httpGet.ProcessCompleted
+=
(()
=>
{
if
(ProcessCompleted
!=
null
)
{
ProcessCompleted();
}
});
}
public
void
BeginLoadPic(
string
url)
{
m_httpGet.BeginGetData(url);
}
public
void
RevokeAsync()
{
m_httpGet.RevokeAsync();
}
}
做一个容器,用来处理多条任务
public
class
RevocableContainer
{
private
class
QueueItem
{
public
GetPicCallback action;
public
string
url;
}
const
int
Threshold
=
3
;
AutoResetEvent m_event;
int
m_count;
bool
m_isThreadProcessing;
Queue
<
QueueItem
>
m_queue;
List
<
IRevocable
>
m_list;
object
m_lock;
public
RevocableContainer()
{
m_event
=
new
AutoResetEvent(
false
);
m_queue
=
new
Queue
<
QueueItem
>
();
m_list
=
new
List
<
IRevocable
>
();
m_lock
=
new
object
();
m_count
=
Threshold;
m_isThreadProcessing
=
false
;
}
void
HttpRequestThread()
{
while
(
true
)
{
if
(m_count
==
0
)
{
m_event.WaitOne();
}
QueueItem item
=
null
;
//
out from queue
lock
(m_queue)
{
if
(
!
m_isThreadProcessing)
{
break
;
}
if
(m_queue.Count
==
0
)
{
break
;
}
item
=
m_queue.Dequeue();
Interlocked.Decrement(
ref
m_count);
}
//
do request
HttpPicGet pic
=
new
HttpPicGet();
pic.OnImageLoadCompleted
+=
(img
=>
{
item.action(img);
});
pic.ProcessCompleted
+=
(()
=>
{
lock
(m_list)
{
m_list.Remove(pic);
}
if
(m_count
==
0
)
{
m_event.Set();
}
Interlocked.Increment(
ref
m_count);
});
pic.BeginLoadPic(item.url);
//
into list
lock
(m_list)
{
m_list.Add(pic);
}
Thread.Sleep(
1
);
}
}
public
void
EnQueue(
string
url, GetPicCallback action)
{
QueueItem item
=
new
QueueItem() { action
=
action, url
=
url };
BackgroundWorker worker
=
null
;
lock
(m_queue)
{
m_queue.Enqueue(item);
if
(
!
m_isThreadProcessing)
{
m_isThreadProcessing
=
true
;
worker
=
new
BackgroundWorker();
}
}
if
(worker
!=
null
)
{
worker.DoWork
+=
((send, ev)
=>
HttpRequestThread());
worker.RunWorkerCompleted
+=
((send, ev)
=>
{
lock
(m_queue)
{
m_isThreadProcessing
=
false
;
}
});
worker.RunWorkerAsync();
}
}
public
void
CancelAll()
{
lock
(m_queue)
{
m_isThreadProcessing
=
false
;
m_queue.Clear();
}
lock
(m_list)
{
foreach
(IRevocable item
in
m_list)
{
item.RevokeAsync();
}
}
}
}
做异步绑定需要的类
public
class
MyImage : INotifyPropertyChanged
{
public
event
PropertyChangedEventHandler PropertyChanged;
string
m_url;
BitmapSource m_source;
public
string
URL
{
get
{
return
m_url; }
set
{
if
(m_url
!=
value)
{
m_url
=
value;
OnPropertyChanged(
new
PropertyChangedEventArgs(
"
URL
"
));
}
}
}
public
BitmapSource Source
{
get
{
return
m_source; }
set
{
if
(m_source
!=
value)
{
m_source
=
value;
OnPropertyChanged(
new
PropertyChangedEventArgs(
"
Source
"
));
}
}
}
protected
virtual
void
OnPropertyChanged(PropertyChangedEventArgs args)
{
if
(PropertyChanged
!=
null
)
PropertyChanged(
this
, args);
}
}
业务的部分都做完了
接下来就是做一个例子来验证成果了
public
partial
class
MainPage : PhoneApplicationPage
{
RevocableContainer m_container
=
new
RevocableContainer();
//
Constructor
public
MainPage()
{
InitializeComponent();
}
private
void
DoClick(
object
sender, RoutedEventArgs e)
{
string
[] sources
=
new
string
[]
{
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526395.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526396.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526397.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526398.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526399.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526400.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526401.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526402.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526403.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526404.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526405.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526406.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526407.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526408.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526409.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526410.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526411.jpg
"
,
"
http://gb.cri.cn/mmsource/images/2008/05/26/ei080526412.jpg
"
};
MyImage[] imgs
=
new
MyImage[sources.Length];
for
(
int
i
=
0
; i
<
imgs.Length;
++
i)
{
imgs[i]
=
new
MyImage();
MyImage imgItem
=
imgs[i];
imgItem.URL
=
sources[i]
+
"
?rand=
"
+
Guid.NewGuid().ToString();
//
加Guid保证调试时无缓存
m_container.EnQueue(imgItem.URL, (bitsource
=>
imgItem.Source
=
bitsource));
}
lbContent.DataContext
=
imgs;
}
private
void
RevokeClick(
object
sender, RoutedEventArgs e)
{
m_container.CancelAll();
}
}
XAML页面代码如下:
<!--
LayoutRoot is the root grid where all page content is placed
-->
<
Grid
x:Name
="LayoutRoot"
Background
="Transparent"
>
<
Grid.RowDefinitions
>
<
RowDefinition
Height
="Auto"
/>
<
RowDefinition
Height
="*"
/>
</
Grid.RowDefinitions
>
<
ListBox
Height
="670"
x:Name
="lbContent"
Grid.Row
="0"
ItemsSource
="
{Binding}
"
>
<
ListBox.ItemTemplate
>
<
DataTemplate
>
<
StackPanel
Orientation
="Horizontal"
>
<
Image
Height
="100"
Width
="100"
Source
="
{Binding Source, Mode=OneWay}
"
/>
<
TextBlock
Text
="
{Binding URL}
"
/>
</
StackPanel
>
</
DataTemplate
>
</
ListBox.ItemTemplate
>
</
ListBox
>
<!--
ContentPanel - place additional content here
-->
<
Grid
x:Name
="ContentPanel"
Grid.Row
="1"
VerticalAlignment
="Bottom"
Margin
="12,0,12,0"
>
<
StackPanel
Orientation
="Horizontal"
>
<
Button
x:Name
="btnDo"
Width
="100"
Height
="100"
Content
="DO"
Click
="DoClick"
/>
<
Button
x:Name
="btnRevoke"
Width
="100"
Height
="100"
Content
="Revoke"
Click
="RevokeClick"
/>
</
StackPanel
>
</
Grid
>
</
Grid
>
运行起来,达到需要所要求的效果