developer82
developer82

Reputation: 13713

Update a file with increment number in a thread-safe way

I have a service that I'm writing in c# that accept request from multiple sources at a a given time. Each request is assigned an ID (simple int) that is incremented for every request. Currently that incremented counter variable is a global variable (which is fine), and I increment it in the following way (which for my understanding and testing is thread safe):

Interlocked.Increment(ref _currentId);

There might be times when the service is restarted, and in such case the variable resets to 0. But upon restart I will actually need the last ID that was assigned.

What I figured I should do is for each time the variable is incremented write it to a file - making sure that the file always contains the latest id.

How can I achieve such a thing in a thread-safe and reliable (that the file always contains the latest id) way ?

Also, performance is very important so I wouldn't like to spend much time on IO operations (I though maybe put the file writing action in a Thread/Task) - what do you suggest?

Thanks

Upvotes: 3

Views: 752

Answers (3)

inquisitive
inquisitive

Reputation: 3639

Here is the fastest that i could think up.

The disk is not as reliable as it looks. even a proper write does not persist as reliably. it could take as long time to pass a write through OS buffers and disk buffers.

In this code, i write as fast as i can, but read/increment even faster. There is always a possibility that some of the IDs are lost, from the last write to the actual state. The only 100% solution is to write and flush in lock-step, but that performance wise is worse.

This sample is a VB-DotNet Console Application.

Imports System.Threading

Module Module1

    Dim globalLong As Long = -1
    Dim globalLongLock As New Object

    Dim fileWriterThread As New Thread(AddressOf writerLoop)
    Dim fileWriterSemaphore As New SemaphoreSlim(0)


    Sub Main()
        fileWriterThread.Start()

        For i = 0 To 100
            Tasks.Parallel.For(0, 100, Sub(n) nextID())
        Next

        Console.WriteLine("done")
        Console.ReadKey()
    End Sub

    Function nextID() As Long
        '
        ' load if required
        '
        If globalLong < 0 Then         '\
            SyncLock globalLongLock    ' note the double IFs
                If globalLong < 0 Then '/

                    'TODO: load from file
                    globalLong = 1

                End If
            End SyncLock
        End If


        '
        ' increment it
        '
        Dim id = Interlocked.Increment(globalLong)


        '
        ' ask writer to store it
        '
        fileWriterSemaphore.Release()

        Return id
    End Function

    Sub writerLoop()
        Dim valueLastWritten As Long = -1

        While True
            fileWriterSemaphore.Wait()

            Dim valueToWrite = globalLong

            If valueToWrite > valueLastWritten Then

                'TODO: write 'valueToWrite' to file
                Console.WriteLine("Persisting " & valueToWrite)
                IO.File.WriteAllText("d:\n.txt", valueToWrite.ToString)

                valueLastWritten = valueToWrite
            End If
        End While
    End Sub

End Module

Upvotes: 1

Deffiss
Deffiss

Reputation: 1136

What about Settings? Go to the properties of your project and choose Settings option there. Then click on hyperlink to create new Settings class (if you currently have no settings). White the name of your Id field, set type and initial value. Then you may use it in code:

static readonly object Sync = new object();
public static void IncrementId()
{
    lock(Sync)
    {
        Console.WriteLine(Properties.Settings.Default.Id);
        Properties.Settings.Default.Id++;
        Properties.Settings.Default.Save();
        Console.WriteLine(Properties.Settings.Default.Id);
    }
}

Upvotes: 0

ken2k
ken2k

Reputation: 48985

Well, the most simple way to achieve this is to use a lock block:

private static object syncObj = new object();
private static int counter = 0;

// Returns the incremented value
private static int Increment()
{
    lock (this.syncObj)
    {
        int currentCount = counter;
        currentCount++;

        // Write currentCount to a file here
        try
        {
            // TODO -> write operation
            // ...

            counter = currentCount;
            return currentCount;
        }
        catch (Exception)
        {
            // Do catch the exception in case the I/O operation failed
            // TODO
        }
    }
}

The question then is: how performance is important for you? I/O operation (such a file write) is expensive. lock could be expensive too.

Upvotes: 0

Related Questions