项目有这样的需求,
要求窗口加载一揽子图片,为了不让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;
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;
先声明两个回调
publicdelegatevoid GetDataStreamCallback(Stream stream);
publicdelegatevoid GetPicCallback(BitmapSource bimage);
publicdelegatevoid GetPicCallback(BitmapSource bimage);
第一个是从网络读取流的回调,第二个是生成了图片源之后的回调.
定义了一个接口:
///<summary>
/// 可以撤销的操作
///</summary>
interface IRevocable
{
void RevokeAsync();
event Action ProcessCompleted;
}
/// 可以撤销的操作
///</summary>
interface IRevocable
{
void RevokeAsync();
event Action ProcessCompleted;
}
一个方法,是用来撤销操作的,
一个事件,是当异步完成时触发的.(不管是正常完成还是撤销,都视为操作完成)
做一个类继承该接口,用来获取网络数据
publicclass HttpResourceGet : IRevocable
{
publicevent GetDataStreamCallback OnDataStreamGenerated;
publicevent 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();
}
});
}
publicvoid BeginGetData(string url)
{
m_client.OpenReadAsync(new Uri(url));
}
publicvoid RevokeAsync()
{
m_client.CancelAsync();
}
}
{
publicevent GetDataStreamCallback OnDataStreamGenerated;
publicevent 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();
}
});
}
publicvoid BeginGetData(string url)
{
m_client.OpenReadAsync(new Uri(url));
}
publicvoid RevokeAsync()
{
m_client.CancelAsync();
}
}
再做一个类,把网络数据包装为图片源
publicclass HttpPicGet : IRevocable
{
publicevent GetPicCallback OnImageLoadCompleted;
publicevent 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();
}
});
}
publicvoid BeginLoadPic(string url)
{
m_httpGet.BeginGetData(url);
}
publicvoid RevokeAsync()
{
m_httpGet.RevokeAsync();
}
}
{
publicevent GetPicCallback OnImageLoadCompleted;
publicevent 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();
}
});
}
publicvoid BeginLoadPic(string url)
{
m_httpGet.BeginGetData(url);
}
publicvoid RevokeAsync()
{
m_httpGet.RevokeAsync();
}
}
做一个容器,用来处理多条任务
publicclass RevocableContainer
{
privateclass QueueItem
{
public GetPicCallback action;
publicstring url;
}
constint 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 =newobject();
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);
}
}
publicvoid 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();
}
}
publicvoid CancelAll()
{
lock (m_queue)
{
m_isThreadProcessing =false;
m_queue.Clear();
}
lock (m_list)
{
foreach (IRevocable item in m_list)
{
item.RevokeAsync();
}
}
}
}
{
privateclass QueueItem
{
public GetPicCallback action;
publicstring url;
}
constint 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 =newobject();
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);
}
}
publicvoid 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();
}
}
publicvoid CancelAll()
{
lock (m_queue)
{
m_isThreadProcessing =false;
m_queue.Clear();
}
lock (m_list)
{
foreach (IRevocable item in m_list)
{
item.RevokeAsync();
}
}
}
}
做异步绑定需要的类
publicclass MyImage : INotifyPropertyChanged
{
publicevent PropertyChangedEventHandler PropertyChanged;
string m_url;
BitmapSource m_source;
publicstring 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"));
}
}
}
protectedvirtualvoid OnPropertyChanged(PropertyChangedEventArgs args)
{
if (PropertyChanged !=null)
PropertyChanged(this, args);
}
}
{
publicevent PropertyChangedEventHandler PropertyChanged;
string m_url;
BitmapSource m_source;
publicstring 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"));
}
}
}
protectedvirtualvoid OnPropertyChanged(PropertyChangedEventArgs args)
{
if (PropertyChanged !=null)
PropertyChanged(this, args);
}
}
业务的部分都做完了
接下来就是做一个例子来验证成果了
publicpartialclass MainPage : PhoneApplicationPage
{
RevocableContainer m_container =new RevocableContainer();
// Constructor
public MainPage()
{
InitializeComponent();
}
privatevoid DoClick(object sender, RoutedEventArgs e)
{
string[] sources =newstring[]
{
"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;
}
privatevoid RevokeClick(object sender, RoutedEventArgs e)
{
m_container.CancelAll();
}
}
{
RevocableContainer m_container =new RevocableContainer();
// Constructor
public MainPage()
{
InitializeComponent();
}
privatevoid DoClick(object sender, RoutedEventArgs e)
{
string[] sources =newstring[]
{
"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;
}
privatevoid 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>
<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>
运行起来,达到需要所要求的效果