Monotouch线程 - 请求的顺序/优先级

时间:2013-07-03 18:43:27

标签: c# xamarin.ios

使用UICollectionView时,我遇到了一些性能问题,其中单元格正在显示图像(从HD加载)。

我通过在后台加载图片解决了这个问题。

基本上,在我的“GetCell”方法中,我检查图像是否在我的ImageCache中。

  • 如果是这样,请在单元格中的ImageView上设置图像。
  • 如果没有,请在后台加载图像并请求重新加载该特定图像 item(我请求重新加载,因为我不知道该单元是否被回收 同时,所以直接设置图像是不安全的)

我的后台流程片段:

ThreadPool.QueueUserWorkItem (delegate { 
                    ImagesCache.AddImageToCache(""+indexPath.Row,ImageForPosition(indexPath.Row));
                    InvokeOnMainThread (delegate { 
                        collectionView.ReloadItems(Converter.ToIndexPathArray(indexPath));
                    });
                }); 

它工作正常,但是如果你快速滚动,它将加载所有这些异步任务,问题是它将按顺序执行请求(FIFO)。因此,当您快速滚动时,将在可见单元格的图像之前加载不可见单元格的图像。

有没有人知道如何优化此流程以获得更好的用户体验? 因为如果我将此扩展到包含来自互联网的图像,问题将更糟(因为下载)。

增加同时线程的最大数量将允许后来添加的线程立即启动但会降低整体性能/下载速度,因此这也不是真正的解决方案。

谢谢, 马特

1 个答案:

答案 0 :(得分:0)

我的项目简介:一个thread下载queue支持的图片。另外检查目标UI控件,它没有出列以便重复使用。

长版:

  • 使用方法queue / Start实施Stop。当Start正在调用时,启动后台线程,在繁忙循环(while true { DoSomething(); })中将尝试从队列中出队请求。如果没有出队,请稍微睡一觉。如果出列,请执行它(下载图像)。 Stop方法应该说线程退出循环:
public void Start()
{
    if (started) {
        return;
    }
    started = true;

    new Thread (new ThreadStart (() => {
        while (started) {
            var request = GetRequest();
            if (request != null) {
                request.State = RequestState.Executing;
                Download (request);
            } else {
                Thread.Sleep (QueueSleepTime);
            }
        }
    })).Start ();
}

public void Stop()
{
    started = false;
}
  • 然后,在queue中创建一个私有方法来下载具有这种逻辑的图像:检查文件缓存中的图像。如果文件可用,请阅读并返回。如果没有,请下载,保存到文件,返回(调用Action<UIImage> onDownload)或错误(调用Action<Exception> onError)。在queue的忙循环中调用此方法。将其命名为Download
public Download(Request request)
{
    try {
        var image = GetImageFromCache(request.Url);
        if (image == null) {
            image = DownloadImageFromServer(request.Url); // Could be synchronous
        }
        request.OnDownload(image);
    } catch (Exception e) {
        request.OnError(e);
    }
}
  • 然后,创建一个公共方法来向队列添加请求。模式Command对于包装队列请求非常有用:存储Actions,当前State。将其命名为DownloadImageAsync
public DownloadImageAsync(string imageUrl, Action<UIImage> onDownload, Action<Exception> onError)
{
    var request = MakeRequestCommand(imageUrl, onDownload, onError);
    queue.Enqueue(request);
}
  • UITableViewCell准备展示并请求下载图片时:
// Custom UITableViewCell method, which is called in `UITableViewSource`'s `GetCell`
public void PrepareToShow()
{
    var imageURLClosure = imageURL;
    queue.DownloadImageAsync(imageURL, (UIImage image) => {
        if (imageURLClosure == imageURL) {
            // Cell was not dequeued. URL from request and from cell are equals.
            imageView.Image = image;
        } else {
            // Do nothing. Cell was dequeued, imageURL was changed.
        }
    }, (Exception e) => {
        // Set default image. Log.
    });
}

检查(imageURLClosure == imageURL)非常重要,以避免在快速滚动时在一个UIImageView中显示多个图像。一个单元可以初始化多个请求,但只应使用最后一个结果。

进一步改进:

  • LIFO执行。如果尚未运行任何请求,请添加新的请求以开始。
  • 使用Action<byte[]> onDownload代替Action<UIImage> onDownload进行跨平台代码兼容;
  • 当单元格成为不可分割的时候取消download image请求的可用性(WillMoveToSuperview)。嗯,这不是很必要。首次下载后,图像将在缓存中,因此任何进一步的图像请求都将快速完成。感谢缓存;
  • 内存缓存。因此,在最坏的情况下,链将是:

Find image in in-memory cache - &gt; Find image in file cache - &gt; Downloading from server