• 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;

    先声明两个回调

    publicdelegatevoid GetDataStreamCallback(Stream stream);
    publicdelegatevoid GetPicCallback(BitmapSource bimage);

    第一个是从网络读取流的回调,第二个是生成了图片源之后的回调.

    定义了一个接口:

    ///<summary>
    /// 可以撤销的操作
    ///</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();
    }

    }

    再做一个类,把网络数据包装为图片源

    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();
    }

    }

    做一个容器,用来处理多条任务

    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();
    }
    }
    }
    }

    做异步绑定需要的类

    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);
    }
    }

    业务的部分都做完了

    接下来就是做一个例子来验证成果了

    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();
    }
    }

    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>

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

  • 相关阅读:
    @RenderBody()和@RenderSection()
    C# async await 死锁问题总结
    [小技巧]你真的了解C#中的Math.Round么?
    ASP.NET MVC
    api接口返回动态的json格式?我太难了,尝试一下 linq to json
    bootstrap-table表头固定,表内容不对齐的问题
    Windows下Nginx反向代理
    Windows下Nginx的启动停止等基本操作命令详解
    Asp.NET websocket,Asp.NET MVC 使用 SignalR 实时更新前端页面数据
    Asp.NET websocket,Asp.NET MVC 使用 SignalR 实现推送功能一(Hubs 在线聊天室)
  • 原文地址:https://www.cnblogs.com/gibbon/p/2009704.html
Copyright © 2020-2023  润新知