Reputation: 13713
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
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
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
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