Search code examples
c#web-servicessynchronizationmemory-mapped-files

Does a MemoryMappedFile with a single writer require synchronization


I have a web service that processes Excel files (previously uploaded to the server), which involves a lot of database updates, and takes quite a while. I'd like to provide another web service that can be called to check the progress of this process. It would take the ID of the process (likely a GUID) as input and return an int (0-100) indicating the progress.

My thought is that the long-running service could create a memory-only MemoryMappedFile (MMF)—only sizeof(int) bytes would be needed—where it would, at some interval, report its progress. The "progress" service could then open the MMF, read the int, and report back to the caller.

I have two questions. First, I don't have much experience with MMFs, so is there some pitfall here that I'm possibly unaware of? Secondly, and more to the point, is synchronization necessary in this scenario, given that there will only be a single writer? After searching a bit, I wasn't able to discover if MemoryMappedViewAccessor.Write is atomic for ints.


Solution

  • I wasn't sure at first since I haven't used MMFs for IPC before, but I tested this now using the samples on MSDN.

    The short answer is NO, synchronization is not necessary (and this makes sense if you think about it). You have below both samples - writer process and reader process, as console apps.

    The only catch is that you want to either reset the cursor position in the write/read streams to 0 after each read/write or alternatively create a view map for the first 32 bits. Not sure if the second works (most likely not and you still have to reset the file pointer).

    Writer process:

        static void Main(string[] args)
        {
            using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("testmap", 10000))
            {
                int i = 0;
                using (MemoryMappedViewStream stream = mmf.CreateViewStream())
                {
                    using (BinaryWriter writer = new BinaryWriter(stream))
                    {
                        while (i++ < 10000)
                        {
                            writer.Write(i);
                            Thread.Sleep(50);
                            writer.Seek(0, SeekOrigin.Begin);
                        }
                    }
                }
    
                Console.WriteLine("Done");
                Console.ReadLine();
            }
        }
    

    Reader process:

        static void Main(string[] args)
        {
            try
            {
                using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("testmap"))
                {
                    using (MemoryMappedViewStream stream = mmf.CreateViewStream())
                    {
                        using (BinaryReader reader = new BinaryReader(stream))
                        {
                            int value = 0;
                            while (value != 10000)
                            {
                                value = reader.ReadInt32();
                                Console.WriteLine("Process A says: {0}", value);
                                stream.Seek(0L, SeekOrigin.Begin);
                                Thread.Sleep(20);
                            }
                        }
                    }
                }
            }
            catch (FileNotFoundException)
            {
                Console.WriteLine("Memory-mapped file does not exist. Run Process A first.");
            }
        }
    

    I deliberately made the reader wait less between reads than the writer waits between writes to make sure there are no coincidences.

    The output is as expected, although didn't wait for it to finish:

    Process A says: 85
    Process A says: 85
    Process A says: 85
    Process A says: 86
    Process A says: 86
    Process A says: 87
    Process A says: 87
    Process A says: 87
    Process A says: 88
    Process A says: 88
    Process A says: 89
    Process A says: 89
    Process A says: 89
    Process A says: 90
    Process A says: 90
    Process A says: 91
    Process A says: 91
    Process A says: 91
    Process A says: 92
    Process A says: 92
    Process A says: 93
    Process A says: 93
    Process A says: 93
    Process A says: 94
    

    That being said, I still don't recommend MMF. From your description, you can store without issues the progress statically (maybe in a concurrent collection) in the service that reports the progress. You can solve this by only exposing another endpoint or method which can be called by the second service to update the progress for a specific client/file. When it's done, it just tells it to remove it from memory.