内存映射文件从内存中删除

7
出现了一些问题,当我读取一段时间内的内存映射文件时,它会随机从内存中删除,我不知道发生了什么。是内核或GC从内存中删除了它吗?如果是,请问如何防止它们这样做?
我正在将对象序列化为Json并写入内存。
在读取了几次后,再次尝试读取时出现异常,我会收到“FileNotFoundException:无法找到指定的文件。”
private const String Protocol = @"Global\";

写入内存映射文件的代码:

public  static  Boolean                 WriteToMemoryFile<T>(List<T> data)
        {
            try
            {
                if (data == null)
                {
                    throw new ArgumentNullException("Data cannot be null", "data");
                }

                var mapName = typeof(T).FullName.ToLower();
                var mutexName = Protocol + typeof(T).FullName.ToLower();
                var serializedData = JsonConvert.SerializeObject(data);
                var capacity = serializedData.Length + 1;

                var mmf = MemoryMappedFile.CreateOrOpen(mapName, capacity);
                var isMutexCreated = false;
                var mutex = new Mutex(true, mutexName, out isMutexCreated);
                if (!isMutexCreated)
                {
                    var isMutexOpen = false;
                    do
                    {
                        isMutexOpen = mutex.WaitOne();
                    }
                    while (!isMutexOpen);
                    var streamWriter = new StreamWriter(mmf.CreateViewStream());
                    streamWriter.WriteLine(serializedData);
                    streamWriter.Close();
                    mutex.ReleaseMutex();
                }
                else
                {
                    var streamWriter = new StreamWriter(mmf.CreateViewStream());
                    streamWriter.WriteLine(serializedData);
                    streamWriter.Close();
                    mutex.ReleaseMutex();
                }
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

从内存映射文件读取的代码:

public  static  List<T>                     ReadFromMemoryFile<T>()
        {
            try
            {
                var mapName = typeof(T).FullName.ToLower();
                var mutexName = Protocol + typeof(T).FullName.ToLower();

                var mmf = MemoryMappedFile.OpenExisting(mapName);
                var mutex = Mutex.OpenExisting(mutexName);
                var isMutexOpen = false;
                do
                {
                    isMutexOpen = mutex.WaitOne();
                }
                while (!isMutexOpen);

                var streamReader = new StreamReader(mmf.CreateViewStream());
                var serializedData = streamReader.ReadLine();
                streamReader.Close();
                mutex.ReleaseMutex();
                var data = JsonConvert.DeserializeObject<List<T>>(serializedData);
                mmf.Dispose();
                return data;
            }
            catch (Exception ex)
            {
                return default(List<T>);
            }

        }

你能添加出现异常的那一行吗?你使用的 WriteToMemoryFileReadFromMemoryFile 的调用顺序是什么?你意识到 mmf.Dispose() 会释放 Memory Mapped File 吗? - Ilia Maskov
我只在调用OpenExisting方法后调用Dispose,该方法不会删除内存文件。 - Nikola.Lukovic
没错。我只能假设mmf的终结器是由GC调用的,尝试在WriteToMemoryFile之外保持对mmf的引用。 - Ilia Maskov
2个回答

4
创建内存映射文件的进程必须在您希望其存在期间保留对它的引用。正是因为这个原因,使用CreateOrOpen有点棘手 - 您不知道释放内存映射文件是否会销毁它。
通过在WriteToMemoryFile方法中添加显式的mmf.Dispose(),您可以轻松地看到它的工作原理 - 它将完全关闭文件。在所有对它的引用都超出范围之后,Dispose方法从mmf实例的终结器中调用。
或者,为了更明显地证明GC是罪魁祸首,您可以尝试显式调用GC:
WriteToMemoryFile("Hi");
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();  
ReadFromMemoryFile().Dump(); // Nope, the value is lost now

请注意,我稍微更改了您的方法以使其适用于简单的字符串; 您真正想要的是产生最简单的可能代码来复现您观察到的行为。即使只需要获取JsonConverter也是不必要的复杂化,这可能会导致人们甚至不尝试运行您的代码 :)
另外,当您执行Mutex.WaitOne时,您需要检查AbandonedMutexException-它不是失败,而是表示您掌握了mutex。大多数应用程序都处理此错误,导致死锁问题以及mutex所有权和生命周期问题 :) 换句话说,将AbandonedMutexException视为成功。哦,而且最好将诸如Mutex.ReleaseMutex之类的东西放在finally子句中,以确保它实际发生,即使您遇到异常。线程或进程死亡无关紧要(这只会导致其他竞争者之一获得AbandonedMutexException),但如果您只是遇到使用return false;“处理”的异常,则mutex将不会被释放,直到您关闭所有应用程序并重新开始 :)

嗯,我尝试使用GC.KeepAlive()方法来解决这个问题。但是它没有起到作用。我尝试了GC.KeepAlive(mmf)和GC.KeepAlive(mmf.SafeMemoryMappedFileHandle),但是没有任何改变。我不知道还有什么其他的尝试方法。 - Nikola.Lukovic
1
@Nikola.Lukovic,GC.KeepAlive()的工作方式并非如此,请尝试阅读文档。 KeepAlive只是确保在调用GC.KeepAlive()之前不会收集实例。您需要具有一些上下文,其中内存映射文件有效,并且该上下文需要与内存映射文件具有相同的生命周期并引用它。如果不了解应用程序的更多信息,则很难推荐任何具体的做法。 - Luaan
我会阅读文档,感谢您的建议。关于上下文,我有一个在服务中运行的服务器,需要为许多内容拥有配置文件,并且每次调用服务器都需要这些conf文件。因此,为了避免不断调用数据库或在每次调用时使用文件系统进行读/写操作,我考虑使用内存文件。还有第二个应用程序也在同一台机器上运行,需要访问这些内存文件。 - Nikola.Lukovic
@Nikola.Lukovic 如果这两个应用程序都是.NET的,也许最好只使用类似.NET远程调用或WCF等方式进行通信?内存映射文件有点低级且难以高效同步(互斥锁并不便宜,并且您可能最终会大量序列化正在进行的所有工作)。结合一些不错的缓存和合理的缓存失效机制,这可能会非常有效。 - Luaan

0

显然,问题在于MMF失去了其上下文,正如Luaan所解释的那样。但是仍然没有人解释如何执行它:

  1. “写入MMF文件”的代码必须在单独的异步线程上运行。
  2. “从MMF读取”的代码将在读取完成后通知已经读取了MMF。通知可以是例如文件中的标志。

因此,运行“写入MMF文件”的异步线程将持续运行,只要从第二部分读取MMF文件。因此,我们已经创建了内存映射文件有效的上下文。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接