首页 > 代码库 > WP_图片管理机制/异步读取网络图片

WP_图片管理机制/异步读取网络图片

 

项目有这样的需求,

要求窗口加载一揽子图片,为了不让UI阻塞太久,采用异步读取后绑定显示的方案.

图片的下载应该采用并发的过程(等待网络响应会很耗时,一张一张的下载,等待时间太长)

图片的下载不能占用过多的线程数,应有个阀值(图片不是核心业务,不能占用那么多资源)

在图片加载的过程中,如果用户有操作,比如窗口跳转,则未加载完成的图片加载的过程应取消(为了替用户节省流量).

需求就是这么多了,如何实现呢?

思路是这样的,由于需要异步,且需要等待,首先想到使用队列,先让队列排列起来,再定量迭代读取.

因为要涉及异步的取消,想到了用WebClient对象的异步功能, 当然,所以发起异步请求之后的对象我都需要记录,

所以还需要一个list容器.

外部接口是两个参数,url,图片的网址,一个回调,定义了图片下载完成后的操作.

内部的核心流程,

1.将一个图片任务从队列中取出,

2.异步发生此请求,

3.将发起请求的对象放进容器,以备撤销时使用.

撤销的核心流程是.

1.让处理线程停止

2.取消队列中的任务,

3.让等待响应的任务取消.

 

 

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