天天看点

windows phone7 下 Silverlight 异步读取网络图片

项目有这样的需求,

要求窗口加载一揽子图片,为了不让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
   >
        

运行起来,达到需要所要求的效果

windows phone7 下 Silverlight 异步读取网络图片

继续阅读