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

本文关键字:顺序 优先级 请求 线程 单点 | 更新日期: 2023-09-27 18:20:50

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

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

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

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

我的后台流程片段:

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

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

有人知道我如何优化这个过程以获得更好的用户体验吗?因为如果我把它扩展到包括来自互联网的图像,问题会更糟(因为下载)。

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

谢谢,Matt

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

简而言之,我项目的解决方案是:一个thread下载支持queue的图像。再加上在目标UI控件中的检查,它没有出列以供重用。

长版本:

  • 用方法Start/Stop实现queue。当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.
    });
}

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

进一步改进:

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

Find image in in-memory cache->Find image in file cache->Downloading from server