views:

262

answers:

3

I am writing an audit file that is writing the username, time, and the old/changed values of several variables in the application for each user when they use my application. It is using a FileStream and StreamWriter to access the audit file. All audits for each user will be written to the same file.

The issue is that when two users are updating this audit file at the same time, the "old value" of each of the variable is mixing up between the users. Why is this and how can you solve the concurrency problem here?

Some code, shortened for brevity...

Dim fs As FileStream
Dim w As StreamWriter

Public Sub WriteAudit(ByVal filename As String, ByVal username As String, ByVal oldAddress As String, ByVal newAddress As String, ByVal oldCity As String, ByVal newCity As String)
    Dim now As DateTime = DateTime.Now
    Dim audit As String = ""
    audit += now + "," + username + "," + oldAddress + "," + newAddress + "," + oldCity + "," + newCity

    fs = New FileStream(filename, FileMode.Append)
    w = New StreamWriter(fs)
    w.WriteLine(audit)
    w.Close()
    fs.Close()
End Sub

This lives in an AuditLogger class, which is referenced via an instance variable (re-allocated each time the function is accessed).

A: 

A couple of ways:

First, use a shared database not a file. Even a simple access database can handle multiple users much more gracefully than a file on disk.

Second, can you use a separate file for each user? maybe store it in their %APPDATA% folder? Or maybe on a network share somewhere?

Goyuix
I can't do either of those, unfortunately. It is a requirement that no database is involved and it is saved to a .csv file. This must be a central audit for every user to use, rather than an individual one for the thousands of people using it. This audit is for management, not the personal user.
4501
mutiple process file writes are problematic - generally the OS prefers a single process to govern write writes for a single handle. Also a CSV is a database, just a very simple one. Can you have a shared service that the application communicates to for the logging? This sounds to me like a somewhat unreasonable request from management / mandate to do it the wrong way. Just my two cents.
Goyuix
+1  A: 

You might try this:

TextWriter tw = TextWriter.Synchronized(File.AppendText(filePath));

The File.AppendText() method returns a StreamWriter object, which the TextWriter.Synchronized() method wraps to create a thread-safe TextWriter which can be used just like a StreamWriter.

Robert Harvey
Would this still work if the AuditLogger instance is reallocated each time as indicated in the question?
Dave
I don't know, I haven't tried it. Probably the lock wouldn't, now that I think about it. Will edit the answer. The Synchronized Streamwriter documentation only says that it makes the StreamWriter thread safe; it says nothing about file concurrency.
Robert Harvey
+1  A: 

Refactor the application so that you do not have to create a new instance of the AuditLogger class each time. Use the singleton pattern directly, or a dependency-injection framework to use the same instance throughout the application.

From there, the implementation is much easier: surround the write operations with lock statements, or use the TextWriter.Synchronized as has been mentioned in Robert's answer.

This post may be relevant:

Dave
+1 Good answer. Treat it like a logger. There are plenty of those out there that work the way they are supposed to.
Robert Harvey