使用C#清除内存映射文件的内容

10

我正在使用MemoryMappedFile在两个程序之间进行通信。程序“A”创建了一个内存映射文件(mmf),并在定时器上读取其内容。程序“B”在定时器上向mmf写入xml数据。我已经使内存映射正常工作,但遇到一个问题,即前一次迭代的XML数据比当前数据长,旧数据会被带到下一轮中。

为了简单起见,让我们假设程序B写入:

aaaa

程序A将正确读取,

然后程序B的下一次写入是:

b

程序A读取

baaa

似乎应该有一种简单的方法来清空内存映射文件的内容,但我似乎想不出来。很可能我完全错误地处理了这个问题。

以下是我目前正在做的事情。

程序A:

using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("testmap",MemoryMappedFileRights.ReadWrite))
{

    Mutex mutex = Mutex.OpenExisting("testmapmutex");
    mutex.WaitOne();
    string outputtext;
    using (MemoryMappedViewStream stream = mmf.CreateViewStream(0,0))
    {

        XmlSerializer deserializer = new XmlSerializer(typeof(MyObject));
        TextReader textReader = new StreamReader(stream);
        outputtext = textReader.ReadToEnd();
        textReader.Close();

    }

    mutex.ReleaseMutex();
    return outputtext; //ends up in a textbox for debugging

}

程序 B

using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("testmap", MemoryMappedFileRights.ReadWrite))
{

    Mutex mutex = Mutex.OpenExisting("testmapmutex");
    mutex.WaitOne();

    using (MemoryMappedViewStream stream = mmf.CreateViewStream(0, 0))
    {


        XmlSerializer serializer = new XmlSerializer(typeof(MyObject));
        TextWriter textWriter = new StreamWriter(stream);
        serializer.Serialize(textWriter, myObjectToExport);
        textWriter.Flush();

    }
    mutex.ReleaseMutex();

}

我想知道在这种情况下是否可以使用 stream.SetLength(stream.Position); 来截断文件到当前位置。 - Thomas Weller
你的第一次尝试是偶然成功的,因为MMF仍然包含二进制零,而XmlSerializer会忽略它们。 第二次尝试就没有这么幸运了。传递给CreateViewStream()的参数很重要,必须传递正确的大小。 这是一个非常麻烦的先有鸡还是先有蛋的问题,需要在MMF中解决。 XML不适合MMF还有一个特别讨厌的问题。 在托管代码中优点虽少,但您根本没有享受到MMF的好处,使用命名管道要好得多。 - Hans Passant
1
你说得都对。昨天有人指出我应该使用命名管道,但是后来又删除了他的回答。今天我把整个东西都换过来了,我觉得这对我会有用。 - bfayer
我应该删除这个问题吗?因为我已经放弃了这种策略,转而使用命名管道。 - bfayer
我无法找到一个可接受的解决方案来解决同样的问题,所以我的解决方法是在每个消息中传递一个GUID,客户端将其跟踪为LastGuid,然后在反序列化时,如果当前消息的GUID相同,则忽略该消息。仍然感觉像是一个糟糕的hack解决方法,对于本应该很简单的事情。 - nathanchere
2个回答

0
假设length相对较小,你可以真正地清除它。
textWriter.BaseStream.Seek(0, System.IO.SeekOrigin.Begin);
textWriter.BaseStream.Write(new byte[length], 0, length);
textWriter.BaseStream.Seek(0, System.IO.SeekOrigin.Begin);

编辑:我想我误解了OP的问题。他遇到的问题不是清除MMF的内容,而是流操作。这应该可以解决问题:

textWriter.BaseStream.Seek(0, System.IO.SeekOrigin.Begin);
textWriter.Write("");
textWriter.Flush();

话虽如此,你可能想要两者兼备。


-1

我并没有真正地使用过MemoryMappedStreams,但这个问题似乎很有趣,所以我试着解决了一下。我写了一个非常基本的Windows示例,其中包含两个按钮(读/写)和一个文本框。我没有在CreateViewStream调用中传入“0, 0”,而是使用“CreateOrOpen”调用创建了一个固定长度的文件,并且一切都运行良好!以下是我编写的关键代码:

写入文件

// create the file if it doesn't exist
if (sharedFile == null) sharedFile = MemoryMappedFile.CreateOrOpen("testmap", 1000, MemoryMappedFileAccess.ReadWrite);

// process safe handling
Mutex mutex = new Mutex(false, "testmapmutex");

if (mutex.WaitOne()) {
    try {
        using (MemoryMappedViewStream stream = sharedFile.CreateViewStream()) {
            var writer = new StreamWriter(stream);
            writer.WriteLine(txtResult.Text);
            writer.Flush();
        }
    }
    finally { mutex.ReleaseMutex(); }
}

读取文件

// create the file if it doesn't exist
if (sharedFile == null) sharedFile = MemoryMappedFile.CreateOrOpen("testmap", 1000, MemoryMappedFileAccess.ReadWrite);

// process safe handling
Mutex mutex = new Mutex(false, "testmapmutex");

if (mutex.WaitOne()) {
    try {
        using (MemoryMappedViewStream stream = sharedFile.CreateViewStream()) {
            var textReader = new StreamReader(stream);
            txtResult.Text = textReader.ReadToEnd();
            textReader.Close();
        }
    }
    finally { mutex.ReleaseMutex(); }
}

处理文件(完成后)

if (sharedFile != null) sharedFile.Dispose();

完整的示例请参见此处:https://github.com/goopyjava/memory-map-test。希望能帮到你!

编辑/注释 - 如果您查看提供的示例,您可以随意多次写入文件,并且每次读取时都将仅读取最后一次写入的内容。我相信这是问题的最初目标。


你根本没有解决原帖作者的问题,你只是走了他的第一步而已。 - Hans Passant
@HansPassant 如果您尝试运行我发布的测试程序,它实际上每次运行读取或写入代码块时都会“清除文件”。本质上,通过在字符串末尾写入空字符,这似乎可以解决问题 - 不仅是第一次,而且每次之后都可以。换句话说,我解决了问题的核心,即多次写入同一个共享文件。请下载/运行代码,我认为您应该看到相同的结果。 - drew_w
“清除文件”是必要的,不要从您的代码中省略它。MMF 的目的在于提高效率。为了传输一个字节而清除大量字节是低效的。这种“清除”的功能只是个偶然,XmlSerializer 忽略二进制零。1000 字节能正常工作也只是偶然,显然 XML 可能比那更多。有太多的偶然性。 - Hans Passant
@HansPassant 您说得对,拥有一个固定大小的文件可能会有问题。 这是OP没有询问的问题,所以我没有具体讨论这个问题。 我们没有看到他们如何创建原始文件,因此我不知道他们如何处理此标准。 至于“清除”,我的例子的重点是根本不需要这样做-只需使用特殊字符(例如'\0')终止即可。 在我的示例中,如果您在序列化时以空字符终止,则可以多次读写而无需清除。 我假设xml序列化程序也有类似的选项。 - drew_w
嘿,感谢您关注这个问题。我最初也有同样的想法,即通过特殊字符来终止程序,但我无法解决它。昨天有人指出,对于我的应用程序来说,命名管道是更好的选择,所以我今天将所有内容都切换到了命名管道。不过看起来那个答案已经被删除了。 - bfayer

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