Monday, April 25, 2011

Having problem opening/writing to a text file in ASP.NET

I want to write some stats to a text file every time a person loads a page. But every once in awhile I am getting at 'Could Not Open File, Already in use' type of error. I can not 100% replicate this error it is very erratic. My code is

Public Sub WriteStats(ByVal ad_id As Integer)
    Dim ad_date As String = Now.Year & Now.Month

    Dim FILENAME As String = Server.MapPath("text/BoxedAds.txt")
    Dim objStreamWriter As StreamWriter
    objStreamWriter = File.AppendText(FILENAME)
    objStreamWriter.WriteLine(ad_id & ";" & ad_date)
End Sub

My question is, how can I lock and unlock the file so I stop getting the erratic errors?


From stackoverflow
  • If two or more requests hit your web server at roughly the same time, they will all try to open the same file. You will need to create unique file names for each request.

    Joel Coehoorn : Actually: each session is probably good enough. It's unlikely for a single user be able to create that many simultaneous requests.
  • You will have to handle the exception and build some handling to re-try writing to the file after a short random interval.

    If you get too much contention then it might make more sense to log it to a table in a database and create a process to export to a file (if its still needed)

    Bruno43 : I sometimes need to write to the file up to 20 times per page how big of a strain would that be on a database?
  • I haven't had any trouble with short info using: File.AppendAllText(path, info);

    Regarding the comment on it causing locks, from reflector it uses the same options explained very well by Joel. It does not use the trace writer, so it won't output to a temp file in the case of high load / large content causing trouble.

    If the info is large, you really want separate files. For high load, I would go with Joel's suggestion and create a temp file, which can be alternatively done by catching the exception on File.AppendAllText, and using the same File.AppeandAllText with a unique filename.

    Joel Coehoorn : It can still lock: I've seen it.
    eglasius : are you sure, this is on production and not a single log entry for that ... as I said, small bits of info
    eglasius : @Joel I confirmed it uses the appropriate options with reflector, do you mean your solution also lock? (don't think so, as I said no log entries on production)
    eglasius : @Joel, I didn't notice your use of the tracewriter generating a temp file if it was locked, updated my answer.
  • Public Sub WriteStats(ByVal ad_id As Integer)
        Dim ad_date As String = Now.Year & Now.Month
        Dim FILENAME As String = Server.MapPath("text/BoxedAds.txt")
        Dim index As Integer
        Using fs As New IO.FileStream(FILENAME, IO.FileMode.Append, IO.FileAccess.Write, IO.FileShare.ReadWrite), _
              tl As New TextWriterTraceListener(fs)
            index = Trace.Listeners.Add(tl)
            Trace.WriteLine(ad_id & ";" & ad_date)
        End Using
    End Sub

    Three important things here:

    1. Use of IO.FileShare.ReadWrite to allow multiple writers on the file at once.
    2. The Using statement to make sure the stream is closed immediately, even if an exception occurs. This will minimize collisions
    3. The TextWriterTraceListener will create a temp file for your if it can't open the file you request, to make sure the message isn't lost.
    eglasius : Joel, no need to go with a custom solution when the simple File.AppendAllText does exactly that (check with reflector)
    Joel Coehoorn : I'll have to do that, but as I said above: I've see File.AppendAllText get lock.
    eglasius : Then your solution also does, I guess we need an alternate solution then / my take is that it wasn't small of info which will give trouble with load regardless of the approach
    eglasius : hmm, nvm, re-read your answer, didn't notice your comment on TextWriterTraceListener using a temp file if it fails opening the file
    Joel Coehoorn : Yeah, I added that a bit later: I still have the code where the file was being locked and went back to reference how we solved it.
    Joel Coehoorn : As an aside: I don't really like this because it preempts the trace class- if you're doing other tracing it can be weird. But I didn't see how else to create such nice temp file in the same folder: the IO.Path.GetRandom__/GetTemp___ methods aren't very helpful.
    eglasius : +1 nice answer + follow up :)
    Bruno43 : With this code I am getting 'Listeners' is not a member of 'System.Web.TraceContext'
    Joel Coehoorn : It's pulling in the wrong namespace: should be the Trace class from System.Diagnostics, not System.Web
    Bruno43 : I imported System.Diagnostics. Still giving me same error.
    Joel Coehoorn : If you'll notice, no where in the code I posted do I ever use "TraceContext". It sounds like you let visual studio autocomplete substitute that in somewhere instead of just "Trace"
    Bruno43 : I do notice that but it doesnt say or use TraceContext anywhere on my side either.
    Joel Coehoorn : In that case, make sure all the types are fully-qualified.
    Bruno43 : It was doing it on any line I had Trace. So I changed it to Diagnostics.Trace. that got rid of the errors but now it doesn't write to the file...
    Bruno43 : Thank you for trying to work with me, but it was easier for me to write error handling.


Post a Comment