C# - 如何使用线程实现图像预加载缓存

发布于 2024-11-05 07:32:28 字数 831 浏览 0 评论 0原文

在我的应用程序中,有一个用户可以单步浏览的图像列表。图像加载速度很慢,因此为了改善用户体验,我想在后台预加载一些图像(例如列表中当前所选图像之后的图像)。

我从来没有真正在 C# 中使用过线程,所以我正在寻找某种“最佳实践”建议,如何实现以下行为:

public Image LoadCachedImage(string path) 
{
    // check if the cache (being operated in the background)
    // has preloaded the image
    Image result = TryGetFromCache(path);

    if (result == null) { result = LoadSynchronously(path); }

    // somehow get a list of images that should be preloaded,
    // e.g. the successors in the list
    string[] candidates = GetCandidates(path);

    // trigger loading of "candidates" in the background, so they will
    // be in the cache when queried later
    EnqueueForPreloading(candidates);

    return result;   
}

我相信,后台线程应该监视队列,并连续处理发布的元素通过EnqueueForPreloading()。我想知道如何实现后台工作线程的“主循环”(或者也许有更好的方法来做到这一点?)

In my application, there is a list of images through which the user can step. Image loading is slow, so to improve user experience I would like to preload some images in the background (e.g. those images in the list succeeding the currently selected one).

I've never really used threads in C#, so I am looking for some kind of "best practice" advice how to implement the following behaviour:

public Image LoadCachedImage(string path) 
{
    // check if the cache (being operated in the background)
    // has preloaded the image
    Image result = TryGetFromCache(path);

    if (result == null) { result = LoadSynchronously(path); }

    // somehow get a list of images that should be preloaded,
    // e.g. the successors in the list
    string[] candidates = GetCandidates(path);

    // trigger loading of "candidates" in the background, so they will
    // be in the cache when queried later
    EnqueueForPreloading(candidates);

    return result;   
}

I believe, a background thread should be monitoring the queue, and consecutively process the elements that are posted through EnqueueForPreloading(). I would like to know how to implement this "main loop" of the background worker thread (or maybe there is a better way to do this?)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

独守阴晴ぅ圆缺 2024-11-12 07:32:28

如果确实需要对候选项进行顺序处理,可以执行以下操作之一:

  1. 创建一个具有 AutoResetEvent 的消息队列数据结构。该类应该生成一个等待事件的线程,然后处理队列中的所有内容。该类的Add或Enqueue应该将其添加到队列中,然后设置事件。这将释放处理队列中项目的线程。

  2. 创建一个启动 STA 线程的类,创建 System.Windows.Forms.Control,然后进入 Application.Run()。每次您想要异步处理图像时,调用 Control.BeginInvoke(...),STA 线程就会在其消息队列中选取它。

可能还有其他选择,但这两个是我会尝试的。

如果您实际上不需要顺序处理,请考虑使用ThreadPool.QueueUserWorkItem(...)。如果有空闲池线程,它将使用它们,否则它将对项目进行排队。但不能保证处理顺序,并且多个可能/将会同时处理。

这是一个(有缺陷的)消息队列示例:

class MyBackgroundQueue<T>
{
    private Queue<T> _queue = new Queue<T>();
    private System.Threading.AutoResetEvent _event = new System.Threading.AutoResetEvent(false);
    private System.Threading.Thread _thread;

    public void Start()
    {
        _thread = new System.Threading.Thread(new System.Threading.ThreadStart(ProcessQueueWorker));
        _thread.Start();
    }

    public class ItemEventArgs : EventArgs
    { public T Item { get; set; } }

    public event EventHandler<ItemEventArgs> ProcessItem;

    private void ProcessQueueWorker()
    {
        while (true)
        {
            _event.WaitOne();
            while (_queue.Count > 0)
                ProcessItem(this, new ItemEventArgs { Item = _queue.Dequeue() });
        }
    }

    public void Enqueue(T item)
    {
        _queue.Enqueue(item);
        _event.Set();
    }
}

当然,这里的一个缺陷是 _queue 没有锁定,因此您会遇到竞争条件。但我会让您来解决这个问题(例如使用 2 队列交换方法)。另外, while(true) 永远不会中断,但我希望该示例能够满足您的目的。

If you really need sequential processing of the candidates, you can do one of the following:

  1. Create a message queue data structure that has a AutoResetEvent. The class should spawn a thread that waits on the event and then processes everything in the queue. The class's Add or Enqueue should add it to the queue and then set the event. This would release the thread, which processes the items in the queue.

  2. Create a class that starts an STA thread, creates a System.Windows.Forms.Control, and then enters Application.Run(). Every time you want to process an image asynchronously, call Control.BeginInvoke(...) and the STA thread will pick it up in its message queue.

There are probably other alternatives, but these two would be what I would try.

If you don't actually need sequential processing, consider using ThreadPool.QueueUserWorkItem(...). If there are free pool threads, it will use them, otherwise it will queue up the items. But you won't be guaranteed order of processing, and several may/will get processed concurrently.

Here's a (flawed) example of a message queue:

class MyBackgroundQueue<T>
{
    private Queue<T> _queue = new Queue<T>();
    private System.Threading.AutoResetEvent _event = new System.Threading.AutoResetEvent(false);
    private System.Threading.Thread _thread;

    public void Start()
    {
        _thread = new System.Threading.Thread(new System.Threading.ThreadStart(ProcessQueueWorker));
        _thread.Start();
    }

    public class ItemEventArgs : EventArgs
    { public T Item { get; set; } }

    public event EventHandler<ItemEventArgs> ProcessItem;

    private void ProcessQueueWorker()
    {
        while (true)
        {
            _event.WaitOne();
            while (_queue.Count > 0)
                ProcessItem(this, new ItemEventArgs { Item = _queue.Dequeue() });
        }
    }

    public void Enqueue(T item)
    {
        _queue.Enqueue(item);
        _event.Set();
    }
}

One flaw here, of course, are that _queue is not locked so you'll run into race conditions. But I'll leave it to you to fix that (e.g. use the 2 queue swap method). Also, the while(true) never breaks, but I hope the sample serves your purpose.

回忆凄美了谁 2024-11-12 07:32:28

这就是我所说的作弊缓存。操作系统已经为您缓存了文件,但您必须先访问它们。因此,您可以做的只是加载文件,但不保存对它们的引用。

您可以在本身不使用多线程的情况下执行此操作,并且无需将图像保存在列表中。只需创建一个方法委托并为要在后台加载的每个文件调用即可。

例如,预加载目录中的所有 jpeg 图像。

Action<string> d = (string file) => { System.Drawing.Image.FromFile(file); };
foreach(string file in dir.GetFiles("*.jpg"))
    d.BeginInvoke(file);

BeginInvoke() 是一种多线程方法,该循环将非常快,但每个文件将在不同的线程上加载。或者你可以稍微改变一下,将循环放在委托内部,又名。

public void PreCache(List<string> files)
{
    foreach(string file in files)
         System.Drawing.Image.FromFile(file);
}

然后在您的代码中

Action<List<string>> d = PreCache;
d.BeginInvoke(theList);

所有加载都在一个工作线程上完成。

This is what I call cheat caching. The operating system already caches files for you, but you have to access them first. So what you can do is just load the files but don't save a reference to them.

You can do this without multi-threading per-se, and without holding the images in a list. Just create a method delegate and invoke for each file you want to load in the background.

For example, pre-loading all the jpeg images in a directory.

Action<string> d = (string file) => { System.Drawing.Image.FromFile(file); };
foreach(string file in dir.GetFiles("*.jpg"))
    d.BeginInvoke(file);

BeginInvoke() is a multi-threaded approach to this, that loop will go very fast, but each file will be loaded on a different thread. Or you could change that up a little to put the loop inside the delegate, aka.

public void PreCache(List<string> files)
{
    foreach(string file in files)
         System.Drawing.Image.FromFile(file);
}

Then in your code

Action<List<string>> d = PreCache;
d.BeginInvoke(theList);

Then all the loading is done on just one worker thread.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文