项目有这样的需求,
要求窗口加载一揽子图片,为了不让UI阻塞太久,采用异步读取后绑定显示的方案.
图片的下载应该采用并发的过程(等待网络响应会很耗时,一张一张的下载,等待时间太长)
图片的下载不能占用过多的线程数,应有个阀值(图片不是核心业务,不能占用那么多资源)
在图片加载的过程中,如果用户有操作,比如窗口跳转,则未加载完成的图片加载的过程应取消(为了替用户节省流量).
需求就是这么多了,如何实现呢?
思路是这样的,由于需要异步,且需要等待,首先想到使用队列,先让队列排列起来,再定量迭代读取.
因为要涉及异步的取消,想到了用WebClient对象的异步功能, 当然,所以发起异步请求之后的对象我都需要记录,
所以还需要一个list容器.
外部接口是两个参数,url,图片的网址,一个回调,定义了图片下载完成后的操作.
内部的核心流程,
1.将一个图片任务从队列中取出,
2.异步发生此请求,
3.将发起请求的对象放进容器,以备撤销时使用.
撤销的核心流程是.
1.让处理线程停止
2.取消队列中的任务,
3.让等待响应的任务取消.
monospace; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; width: 100%; background-color: white">using System;using System.Windows;using System.Windows.Media.Imaging;using Proj.Interface;namespace Proj.Common{ /// <summary> /// 把网络数据包装为图片源 /// </summary>public class HttpPicGet : IRevocable
{public event GetPicCallback OnImageLoadCompleted;
public event Action ProcessCompleted;
/// <summary> /// 当前正在处理的URL /// </summary>public string Url;
HttpResourceGet m_httpGet;
public HttpPicGet() { m_httpGet = new HttpResourceGet();m_httpGet.OnDataStreamGenerated += (stream =>
{Deployment.Current.Dispatcher.BeginInvoke(() =>
{ BitmapSource bi = new BitmapImage();bi.SetSource(stream);
if (OnImageLoadCompleted != null)
{OnImageLoadCompleted(bi);
}
});
});
m_httpGet.ProcessCompleted += (() =>
{ //Deployment.Current.Dispatcher.BeginInvoke(() => // {if (ProcessCompleted != null)
{ProcessCompleted();
}
//});});
}
public void BeginLoadPic(string url)
{Url = url;
m_httpGet.BeginGetData(url);
}
public void RevokeAsync()
{m_httpGet.RevokeAsync();
}
}
}
using System;using System.IO;using System.Net;using System.Windows.Media.Imaging;using Proj.Interface;namespace Proj.Common{ /// <summary> /// 从网络读取流的回调 /// </summary>public delegate void GetDataStreamCallback(Stream stream);
/// <summary> /// 生成了图片源之后的回调 /// </summary>public delegate void GetPicCallback(BitmapSource bimage);
/// <summary> /// 获取网络数据 /// </summary>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)
{if (url.Contains("?"))
{url += "&rand=" + Guid.NewGuid();//加Guid保证调试时无缓存
}
else {url += "?rand=" + Guid.NewGuid();//加Guid保证调试时无缓存
}
m_client.OpenReadAsync(new Uri(url));}
public void RevokeAsync()
{m_client.CancelAsync();
}
}
}
using System.ComponentModel;using System.Windows.Media.Imaging;namespace Proj.Common{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);}
}
}
using System.Collections.Generic;using System.ComponentModel; using System.Threading; using Proj.Interface;namespace Proj.Common{ /// <summary> /// 容器,用来处理多条任务 /// </summary>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();
}
}
/// <summary> /// 取消全部,并返回未完成的(正在进行的及未开始的) /// </summary>public List<string> CancelAll()
{List<string> unFinishedUrls=new List<string>();
lock (m_queue) { m_isThreadProcessing = false; if (m_queue.Count > 0) {foreach (var queueItem in m_queue)
{unFinishedUrls.Add(queueItem.url);
}
}
m_queue.Clear();
}
lock (m_list) {foreach (IRevocable item in m_list)
{HttpPicGet picGet = (HttpPicGet)item;
unFinishedUrls.Add(picGet.Url);
item.RevokeAsync();
}
}
return unFinishedUrls;}
}
}
界面层:
using System;using System.Collections.Generic;using System.Linq;using System.Windows;using Proj.Common;using Microsoft.Phone.Controls;namespace Proj{public partial class PageImgTest : PhoneApplicationPage
{ RevocableContainer m_container = new RevocableContainer();List<string> _unFinishUrls = new List<string>();
List<string> sources = new List<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"};
// Constructor public PageImgTest() {InitializeComponent();
}
private void DoClick(object sender, RoutedEventArgs e)
{ if (_unFinishUrls.Count > 0) {StartLoad(_unFinishUrls);
}
else {StartLoad(sources);
}
}
private void RevokeClick(object sender, RoutedEventArgs e)
{_unFinishUrls = m_container.CancelAll();
MessageBox.Show("未完成数:" + _unFinishUrls.Count);}
private void BtnRetry_OnClick(object sender, RoutedEventArgs e)
{if (!_isInit || lbContent.Items.Count == 0) return;
//将未完成的图片继续加载StartLoad(_unFinishUrls);
}
private bool _isInit = false;
/// <summary> /// 加载/继续加载未完成的 /// </summary> void StartLoad(List<string> argImgUrls)
{if (argImgUrls == null || argImgUrls.Count == 0) return;
List<MyImage> imgs = new List<MyImage>(); //MyImage[] imgs = new MyImage[sources.Count]; //for (int i = 0; i < argImgUrls.Count; ++i) //{ // MyImage imgItem = new MyImage(); // imgs.Add(imgItem); // //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)); //} if (!_isInit) {for (int i = 0; i < argImgUrls.Count; ++i)
{ MyImage imgItem = new MyImage();imgs.Add(imgItem);
imgItem.URL = sources[i];
m_container.EnQueue(imgItem.URL, (bitsource => imgItem.Source = bitsource));
}
lbContent.DataContext = imgs;
}
else {for (int i = 0; i < argImgUrls.Count; ++i)
{ MyImage imgItem = new MyImage();imgs.Add(imgItem);
imgItem.URL = argImgUrls[i];
m_container.EnQueue(imgItem.URL, (bitsource => imgItem.Source = bitsource));
}
for (int i = 0; i < lbContent.Items.Count; i++)
{MyImage myImg = (MyImage)lbContent.Items[i];
var item = (from c in argImgUrls where c == myImg.URL select c).FirstOrDefault();
if (!string.IsNullOrEmpty(item))//匹配上
{m_container.EnQueue(item, (bitsource => myImg.Source = bitsource));
}
}
}
if (!_isInit && imgs.Count > 0) { _isInit = true;UpdateLayout();
}
}
}
}
<phone:PhoneApplicationPage
x:Class="Proj.PageImgTest" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}"SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d" shell:SystemTray.IsVisible="True"><!--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" />
<Button x:Name="btnRetry" Width="120" Height="100" Content="Retry" Click="BtnRetry_OnClick" />
</StackPanel>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
参考地址:http://blog.csdn.net/antsnm/article/details/6738292