Sagar
Sagar

Reputation: 490

C# Read/Write file from multiple applications

I have scenario where we are maintaining Rates file(.xml) which is access by 3 different application running on 3 different servers. All 3 application uses RatesMaintenance.dll which has below 4 methods Load, Write, Read and Close.

All 3 applications writes into file continuously and hence I have added Monitor.Enter and Monitor.Exit mechanism assuming that these 3 operation from 3 different application will not collide. But at this moment, in some case, I am getting error - "Could not open the rates file"

As per my understanding, this means, some reason 3 application tries to access at same. Could anyone please suggest how to handle such scenario?

        Monitor.Enter(RatesFileLock);

        try
        {
            //Open Rates file
            LoadRatesFile(false);

            //Write Rates into file
            WriteRatesToFile();

            //Close Rates file
            CloseRatesFile();
        }
        finally
        {
            Monitor.Exit(RatesFileLock);
        }

Method signature of Load-

LoadRatesFile(bool isReadOnly)

For opening file-

new FileStream(RatesFilePath,
        isReadOnly ? FileMode.Open : FileMode.OpenOrCreate,
        isReadOnly ? FileAccess.Read : FileAccess.ReadWrite,
                isReadOnly ? FileShare.ReadWrite : FileShare.None);

.... remaining Rates reading logic code here

For reading Rates from file-

Rates = LoadRatesFile(true);

For Writing Rates into file-

if (_RatesFileStream != null && _RatesInfo != null && _RatesFileSerializer != null)
            {
                    _RatesFileStream.SetLength(0);

                    _RatesFileSerializer.Serialize(_RatesFileStream, _RatesInfo);
            }

In closing file method-

            _RatesFileStream.Close();
            _RatesFileStream = null;

I hope, I try to explain my scenario in details. Please let me know in case anyone more details.

Upvotes: 2

Views: 3227

Answers (2)

Dan Roberts
Dan Roberts

Reputation: 2329

While the other answers are correct and that you won't be able to get a perfect solution with files that are accessed concurrently by multiple processes, adding a retry mechanism may make it reliable enough for your use case.

Before I show one way to do that, I've got two minor suggestions - C#'s "using" blocks are really useful for dealing with resources such as files and locks that you really want to be sure to dispose of after use. In your code, the monitor is always exited because you use try..finally (though this would still be clearer with an outer "lock" block) but you don't close the file if the WriteRatesToFile method fails.

So, firstly, I'd suggest changing your code to something like the following -

private static object _ratesFileLock = new object();

public void UpdateRates()
{
    lock (_ratesFileLock)
    {
        using (var stream = GetRatesFileStream())
        {
            var rates = LoadRatesFile(stream);

            // Apply any other update logic here

            WriteRatesToFile(rates, stream);
        }
    }
}

private Stream GetRatesFileStream()
{
    return File.Open("rates.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
}

private IEnumerable<Rate> LoadRatesFile(Stream stream)
{
    // Apply any other logic here
    return RatesSerialiser.Deserialise(stream);
}

private void WriteRatesToFile(IEnumerable<Rate> rates, Stream stream)
{
    RatesSerialiser.Serialise(rates, stream);
}

This tries to opens the file stream once and then reuses it between load and write actions - and reliably dispose of it, even if an error is encountered inside in the using block (same applies to the "lock" block, which is simpler than Monitor.Enter/Exit and try..finally).

This could quite simply be extended to include a retry mechanism so that if the file is locked by another process then we wait a short time and then try again -

private static object _ratesFileLock = new object();

public void UpdateRates()
{
    Attempt(TryToUpdateRates, maximumNumberOfAttempts: 50, timeToWaitBetweenRetriesInMs: 100);
}

private void TryToUpdateRates()
{
    lock (_ratesFileLock)
    {
        using (var stream = GetRatesFileStream())
        {
            var rates = LoadRatesFile(stream);

            // Apply any other update logic here

            WriteRatesToFile(rates, stream);
        }
    }
}

private Stream GetRatesFileStream()
{
    return File.Open("rates.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
}

private IEnumerable<Rate> LoadRatesFile(Stream stream)
{
    // Apply any other logic here
    return RatesSerialiser.Deserialise(stream);
}

private void WriteRatesToFile(IEnumerable<Rate> rates, Stream stream)
{
    RatesSerialiser.Serialise(rates, stream);
}

private static void Attempt(Action work, int maximumNumberOfAttempts, int timeToWaitBetweenRetriesInMs)
{
    var numberOfFailedAttempts = 0;
    while (true)
    {
        try
        {
            work();
            return;
        }
        catch
        {
            numberOfFailedAttempts++;
            if (numberOfFailedAttempts >= maximumNumberOfAttempts)
                throw;
            Thread.Sleep(timeToWaitBetweenRetriesInMs);
        }
    }
}

Upvotes: 3

Scott Hannen
Scott Hannen

Reputation: 29207

What you're trying to do is difficult bordering on impossible. I won't say that it's impossible because there is always a way, but it's better not to try to make something work in a way it wasn't intended to.

And even if you get it to work and you could ensure that applications on multiple servers don't overstep each other, someone could write some other process that locks the same file because it doesn't know about the system in place for gaining access to that file and playing well with the other apps.

You could check to see if the file is in use before opening it, but there's no guarantee that another server won't open it in between when you checked and when you tried to open it.

The ideal answer is not to try to use a file as database accessed by multiple applications concurrently. That's exactly what databases are for. They can handle multiple concurrent requests to read and write records. Sometimes we use files for logs or other data. But if you've got applications going on three servers then you really need a database.

Upvotes: 3

Related Questions