【问题标题】:Using threading to generate an expensive file and handle other requests for that file使用线程生成昂贵的文件并处理对该文件的其他请求
【发布时间】:2023-03-15 06:51:02
【问题描述】:

我正在编写一个生成、缓存和提供 zip 文件的 Web 服务。

如果缓存中不存在请求的文件,则会生成该文件,然后提供该文件。根据请求,生成此文件可能需要相当长的时间。同一 zip 文件的另一个请求可能会进入,因为它仍在第一个请求中生成。

一个基本的场景可能是这样的

  • 线程 1:给我 bigfile.zip
  • 线程 1:bigfile.zip 不存在
  • 线程 1:生成 bigfile.zip
  • 线程 2:给我 bigfile.zip
  • 线程 2:线程 1 正在生成 bigfile.zip - 等待它完成
  • 线程1:完成生成bigfile.zip
  • 线程 1:服务 bigfile.zip
  • 线程 2:服务 bigfile.zip

所以我正在考虑使用Thread 来实现这一点,并在文件准备好后使用Join() 来同步它们。

但是我有一个问题。我将如何管理多个不同文件的多个请求?我正在考虑使用Dictionary<fileId, Thread> 来跟踪它们,但是当它完成它的过程时,我怎样才能安全地从字典中删除一个线程呢?如果不对整个事情进行锁定,我看不到任何方法 - 包括实际过程本身。当然,这样做似乎首先会使线程的整个想法变得多余。

lock(_myLocker)
{
    if(!fileThreads.containsKey(fileId))
    {
        Thread myThread = MakeMeAThread();
        fileThreads.add(fileId, myThread);
    }
    fileThreads[fileId].Join();    
    //We have to do the Join inside the lock, this is the only way we know (in a threadsafe manner) that the dictionary definitely contains our key
}
ServeTheFile();
//How do I clean up the no longer required fileThreads[fileId]?

为了增加难度,还有另一种使用服务的方式,它简单地告诉客户端正在请求的文件的状态(不可用 (404)、正在生成、准备好)。

  • 线程 1:给我 bigfile.zip
  • 线程 1:bigfile.zip 不存在
  • 线程 1:生成 bigfile.zip
  • 线程 2:给我 bigfile.zip
  • 线程 2:线程 1 正在生成 bigfile.zip - 等待它完成
  • 线程 3:你有 bigfile.zip 吗? - 不,它正在生成
  • 线程1:完成生成bigfile.zip
  • 线程 1:服务 bigfile.zip
  • 线程 2:服务 bigfile.zip
  • 线程 4:你有 bigfile.zip 吗?是的,它已经为您准备好了
  • 线程 5:你有 invalid.zip 吗?不,这是一个无效请求

那么,您知道为什么我们不能只锁定流程吗?如果我们这样做了,线程 3 就无法被告知文件正在生成,并且必须等待文件生成完成。

【问题讨论】:

  • 为什么不把Join()语句移到锁外呢?
  • 完成后使线程从字典中删除。更好的是,使用任务。这样你就可以附加一个延续。
  • 写完我的答案(见下文)后,我刚刚重新阅读了你的整个问题。您的最后一句话指出,您避免了锁定,因为如果当前生成了请求的文件,则可能存在等待。我对此的问题是:在上述情况下应该发生什么?您是否希望线程将正在生成文件的信息再次返回给客户端,并且客户端必须轮询直到文件可用?
  • 所以不要使用lock,而是使用Monitor.TryEnter等。如果你在ASP.NET中这样做,那么生成和缓存文件的东西可能应该是Windows服务而不是ASP.NET 上下文中的一个线程。
  • @AcidJunkie,是的,客户端必须轮询,直到文件可用。由于它是一个 Web 应用程序,默认方法只是请求文件并等待。但是当我们添加 javascript 时,我们可以使用 ajax 来轮询文件的可用性。

标签: c# .net multithreading threadpool


【解决方案1】:

这是一个非常简单的解决方案:
假设,文件的标识符是它的名称。您可以做的是创建一个包含锁定对象的字典。例如:

Dictionary<string, object> _fileLocks = new Dictionary<string, object>();

所以当一个文件生成请求进来时,你首先锁定字典对象。然后检查它是否已经包含一个锁对象。如果没有,请添加一个。否则获取当前的。

object lockObject;
lock (_fileLocks)
{
    if (_fileLocks.TryGetValue(fileName, out lockObject) == false)
    {
        lockObject = new object();
        _fileLocks.Add(fileName, lockObject);
    }
}

然后锁定锁对象并执行你的工作:

lock (lockObject)
{
    // check if the file has been created. if not, generate it
    // load and return the file
}

这样,对同一文件的其他请求会自动等待,直到它生成。

【讨论】:

  • 不是一个完整的解决方案。您需要添加检查何时从字典中删除该项目,否则您将处于阶段 1 和 2 之间
  • 此解决方案不会从字典中删除任何内容。还是我误解了你的意思?
  • 问题还指出,因为这是一个缓存方案,文件最终会被删除。而且您也没有指出删除文件锁的位置。
  • 我再次指出这一点,您的解决方案只会添加锁,而不会删除它们。什么时候从 _fileLocks 中删除?如果您刚好在 lock(lockObject) 之前并且有人从 _fileLocks 中删除了锁,会发生什么?不是你的方向不好,只是不完整。
  • 你为什么要这样做?因为否则你的字典可能会被永远不会再次使用的锁填满。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多