4
\$\begingroup\$

I've got a job to create a library that would be able to support multithreaded and multi process read and write to a single file. On that single file we would store C# models in an array in JSON format. It would basically work as a mini file system JSON DB.

When the application that uses this lib starts, it would load existing models (if they do exist) in memory, so that we can query the data in memory. Adding, updating and removing of objects would require to flush the data to that single file. I assume that the hardest thing here to achieve would be synchronization between threads and processes. I actually managed to do that with mutex. Currently, I only have two methods, one for writing, and one for reading:

public void Write (string text)
{
    var appGuid = "{FBE403EA-0D59-48D6-941D-E74E0F20B482}";
    var mutexId = $"Global\\{{{appGuid}}}";

    bool createdNew;

    var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
    var securitySettings = new MutexSecurity();
    securitySettings.AddAccessRule(allowEveryoneRule);

    using ( var mutex = new Mutex(false, mutexId, out createdNew, securitySettings) )
    {
        var hasHandle = false;
        try
        {
            hasHandle = mutex.WaitOne(Timeout.Infinite, false);
            using ( var fileStream = new FileStream("test.txt", FileMode.Truncate, FileAccess.Write, FileShare.Read) )
            {
                var buffer = Encoding.UTF8.GetBytes(text);
                fileStream.Write(buffer, 0, buffer.Length);
                fileStream.Close();
            }
        }
        finally
        {
            if ( hasHandle )
                mutex.ReleaseMutex();
        }
    }
}

public string Read ( )
{
    var appGuid = "{FBE403EA-0D59-48D6-941D-E74E0F20B482}";
    var mutexId = $"Global\\{{{appGuid}}}";

    bool createdNew;

    var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
    var securitySettings = new MutexSecurity();
    securitySettings.AddAccessRule(allowEveryoneRule);

    using ( var mutex = new Mutex(false, mutexId, out createdNew, securitySettings) )
    {
        var hasHandle = false;
        try
        {
            hasHandle = mutex.WaitOne(Timeout.Infinite, false);
            using ( var fileStream = new FileStream("test.txt", FileMode.OpenOrCreate, FileAccess.Read, FileShare.Read) )
            {
                var text = "";
                var stringBytes = new byte[fileStream.Length];
                fileStream.Read(stringBytes, 0, (int)fileStream.Length);
                text = Encoding.UTF8.GetString(stringBytes);
                fileStream.Close();
                return text;
            }
        }
        finally
        {
            if ( hasHandle )
                mutex.ReleaseMutex();
        }
    }
}

To test these methods I implemented them in a WPF app, which had two buttons and a textbox. One button would synchronously read a single file in an infinite loop with a wait of 10 ms between cycles and dispatch the text to the textbox. The other button would write to that single file also in an infinite loop with a wait of 10 ms between cycles. In that loop I would download a random text and synchronously write the text to the file.

When there are two instances of that WPF app and they both ran the loops both read and write would work really fast. But after only two more instances read and write would slow down considerably.

Is there anything I can do in these methods to get any boost in performance?

The primary need of this lib is that it always needs to flush data to a file. That's why i'm using Timeout.Infinite when calling WaitOne from a mutex. I can't let it to fail when it tries to access a mutex.

\$\endgroup\$
3
+50
\$\begingroup\$

Is there anything i can do in these methods to get any boost in performance?

Yes, You can reimplement them, using the 'memory-mapped files' (MMf) to solve both problems of parallel concurrency and access sharing.

using (MemoryMappedFile file = MemoryMappedFile.CreateFromFile("test.bin"))
using (MemoryMappedViewStream stream = file.CreateViewStream())
{
    Read(stream);
}

There are two ways of accessing data on a memory mapped file – using a MemoryMappedViewStream and an MemoryMappedViewAccessor. The former is optimised to provide sequential access (e.g. when reading or writing a block of data) and the later is optimised for random access reading and writing.

Also, for 'production code' consider the following:

  • Performance can be greatly improved by always writing data sequentially and in large chunks
  • The chunk size is less important for small amounts of data (less than a kilobyte)
  • There is a cost to creating and disposing accessors, this cost becomes acceptable for reading files below 32K.
  • There is benefit of keeping hold of a stream, wich will remain significant.
  • When reading a large stream the performance is equivalent to raw memory access
\$\endgroup\$
2
\$\begingroup\$

Why are these two lines defined in each method? Shouldn't they be const in your class?

var appGuid = "{FBE403EA-0D59-48D6-941D-E74E0F20B482}";
var mutexId = $"Global\\{{{appGuid}}}";

Same for "test.txt".


This is present in both methods:

var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);

So I'd move that to a method that can be called by both. Considering that you don't do anything with createdNew I'd even move new Mutex(false, mutexId, out createdNew, securitySettings) to that method.


This is an unusual style which doesn't improve readability, IMHO: Write (string text), Read ( ), if ( hasHandle ). You don't seem to do this consistently, anyway.

\$\endgroup\$
  • \$\begingroup\$ thanks for your input! "test.txt", appGuid, mutexId and method names were just for testing, this isn't production ready code yet :) \$\endgroup\$ – Nikola.Lukovic Mar 15 '16 at 16:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.