StreamWriter and/or System.IO.File problems

D

Don

I'm having problems working with a streamwriter object. After closing the
streamwriter and setting it to Nothing, I try to delete the file it was
writing to, but I always get the following error message:

"The process cannot access the file "whatever" because it is being used
by another process."

I've even tried opening another file using the same streamwriter object
before deleting the original file, but it's no use. Something keeps a lock
on that original file, but I can't figure out what.

To reproduce this bug, create a new Windows Application project and create
the following class:


--- START ---


Imports System.IO

Public Class StreamWriterTest

#Region " Constructor/Destructor "

Public Sub New(ByVal filename As String)

' Remember the filename
_filename = filename

' Determine the path we should be storing the log file in
_folder =
System.Environment.ExpandEnvironmentVariables("%WinDir%") & "\Temp\"

' Open the log file
OpenLogFile()

End Sub

Protected Overrides Sub Finalize()

' Close the log file
CloseLogFile()

MyBase.Finalize()

End Sub

#End Region
#Region " Variables "

Private _filename As String = String.Empty ' log file name
Private _folder As String = String.Empty ' folder the log file is
in (always has a trailing \)
Private _logWriter As StreamWriter ' used to write to the
log file

#End Region
#Region " Properties "

Public ReadOnly Property Path() As String
Get
Return _folder & _filename
End Get
End Property
Public ReadOnly Property NewPath() As String
Get
Return _folder & _filename & "_"
End Get
End Property

#End Region
#Region " Methods "

Private Sub OpenLogFile()


Dim sqlLogFileSize As Long = 0


Try

' for debugging purposes
Console.WriteLine("*** Log File Opened : " & Me.Path)

' Create a new streamwriter object
_logWriter = New StreamWriter(Me.Path)

' Make the streamwriter automatically flush changes to the
file to the
' harddisk everytime we use the Write method
_logWriter.AutoFlush = True

' Write a header to the file
_logWriter.WriteLine("test")

Catch ex As Exception

Console.WriteLine("***ERROR : " & ex.Message)

End Try


End Sub
Private Sub CloseLogFile()


' DD Jul 23/04
'
' Closes the log file


Try

_logWriter.Close()
_logWriter = Nothing
GC.Collect()
Console.WriteLine("*** Log File Closed : " & Me.Path)

Try

If IO.File.Exists(Me.NewPath) Then
IO.File.Delete(Me.NewPath)

If Not IO.File.Exists(Me.NewPath) Then
'IO.File.Move(Me.Path, Me.NewPath)
IO.File.Copy(Me.Path, Me.NewPath)
IO.File.Delete(Me.Path)
Console.WriteLine("*** Log File Renamed : " &
Me.NewPath)
Else
Console.WriteLine("*** Log File NOT Renamed.
Another file with the same name already exists and could not be deleted.")
End If

Catch ex As Exception

Console.WriteLine("Error: Log File NOT Renamed: {0}",
ex.Message)

End Try

Catch ex As Exception

Console.WriteLine("Error closing log file: {0}", ex.Message)

End Try


End Sub

#End Region

End Class


--- END ---


Now place a button on Form1 and put the following code in it:


--- START ---

Randomize()

Dim x As StreamWriterTest
x = New StreamWriterTest("test1" & Format(Int(Rnd() * 99999999),
"00000000"))
x = Nothing
GC.Collect()

--- END ---


Looking at the Output Window, you'll see that the IO.File.Delete() raises
the exception. I am at my wits end trying to figure this out.

BTW, I don't think the bug occurs if you try to do the whole thing in, say,
the button's click event. It seems the fact that this code is the Finalize
method of a class has something to do with it.

- Don
 
S

stand__sure

you are setting the varibale to nothing before the call to the
finalizer. additionally, you shouldn't do explicit GC unless
absolutely necessary.

this works fine...
Imports System.IO
Module Module1


Public Class StreamWriterTest

Public Sub New(ByVal filename As String)

' Remember the filename
_filename = filename

' Determine the path we should be storing the log file in
_folder = System.Environment.ExpandEnvironmentVariables("%WinDir%")
& "\Temp\"

' Open the log file
OpenLogFile()

End Sub

Protected Overrides Sub Finalize()

' Close the log file
CloseLogFile()

MyBase.Finalize()

End Sub

Private _filename As String = String.Empty ' log file name
Private _folder As String = String.Empty ' folder the log file is
in (always has a trailing \)
Private _logWriter As StreamWriter ' used to write to the log
file

Public ReadOnly Property Path() As String
Get
Return _folder & _filename
End Get
End Property
Public ReadOnly Property NewPath() As String
Get
Return _folder & _filename & "_"
End Get
End Property

Private Sub OpenLogFile()
Try
Debug.WriteLine("*** Log File Opened : " & Me.Path)
_logWriter = New StreamWriter(Me.Path)
_logWriter.AutoFlush = True
_logWriter.WriteLine("test")
Catch ex As Exception
Debug.WriteLine("***ERROR : " & ex.Message)
End Try

End Sub
Private Sub CloseLogFile()

Try
_logWriter.Close()
Debug.WriteLine("*** Log File Closed : " & Me.Path)
If IO.File.Exists(Me.NewPath) Then IO.File.Delete(Me.NewPath)
Catch ex As Exception
Debug.WriteLine("Error closing log file: {0}", ex.Message)
End Try
End Sub

End Class

Sub main()
Randomize()
Dim x As StreamWriterTest
x = New StreamWriterTest("test1" & Format(Int(Rnd() * 99999999),
"00000000"))
End Sub

End Module
 
D

Don

you are setting the varibale to nothing before the call to the
finalizer.

I don't see what the problem with this is. Setting it to Nothing should
make its Finalize method get called when garbage collection time comes
around. I've never had problems with this.

additionally, you shouldn't do explicit GC unless
absolutely necessary.

That was just to highlight the bug. Taking that line out makes the bug
occur when you shut the program down. It behaves exactly the same way in
either case.

this works fine...

This only works because you removed these lines of code from
StreamWriterTest.CloseLogFile:

If Not IO.File.Exists(Me.NewPath) Then
IO.File.Copy(Me.Path, Me.NewPath)
IO.File.Delete(Me.Path)
Console.WriteLine("*** Log File Renamed : " & Me.NewPath)
Else
Console.WriteLine("*** Log File NOT Renamed. Another file with the
same name " & _
"already exists and could not be deleted.")
End If

If I comment those lines in my original class and leave the rest of the code
intact, it works, too. That's because the file created by the StreamWriter
is never touched after the StreamWriter is closed. If you'll notice in the
code you posted, the file being written to by the StreamWriter is not the
file that is deleted in CloseLogFile(). Me.NewPath gets deleted, not
Me.Path, which is the file we just created with the StreamWriter.

It seems that, after closing the StreamWriter, the file remains locked until
the application itself shuts down...and this happens only if the
StreamWriter is in another class and when it is closed in that class's
Finalize event.

- Don
 
A

_AnonCoward

:
: I'm having problems working with a streamwriter object. After closing
: the streamwriter and setting it to Nothing, I try to delete the file
: it was writing to, but I always get the following error message:
:
: "The process cannot access the file "whatever" because it is being
: used by another process."
:
: I've even tried opening another file using the same streamwriter
: object before deleting the original file, but it's no use. Something
: keeps a lock on that original file, but I can't figure out what.
:
: To reproduce this bug, create a new Windows Application project and
: create : the following class:


<snip original code sample>


: Looking at the Output Window, you'll see that the IO.File.Delete()
: raises the exception. I am at my wits end trying to figure this out.
:
: BTW, I don't think the bug occurs if you try to do the whole thing in,
: say, the button's click event. It seems the fact that this code is
: the Finalize method of a class has something to do with it.
:
: - Don


I can't reproduce this. I copied your code into a console app with a few
changes. I removed your original comments and added new comments to flag
my changes (highlighted below). I only made two changes of any note:


* I added a new function 'ZapThisPuppyNow' to the class so I could
force a finalize event.

* And I added a test to the close log file function to ensure the
_logWriter object exists before I try to access it.

Apart from that, the core of your code is unchanged.

------------------------------------------
'[ADDED IMPORTS]
Imports Microsoft.VisualBasic
Imports System
Imports System.IO

'[ADDED SUB MAIN (REPLACES BUTTON EVENT CODE)]
public Class [class]
Public Shared Sub Main
Console.WriteLine("Enter Main" & vbCrLf)

Console.WriteLine("Creating Object" & vbCrLf)
Dim swt As New StreamWriterTest("swt.test")

Console.WriteLine("Finalizing Object" & vbCrLf)
swt.ZapThisPuppyNow

Console.WriteLine("Releasing Object" & vbCrLf)
swt = Nothing

Console.WriteLine("Exit Main" & vbCrLf)
End Sub
End Class


Public Class StreamWriterTest

'[ADD FUNCTION TO EXPLICITLY CALL FINALIZE]
Public Sub ZapThisPuppyNow()
Finalize
End Sub

'[ADD CONSOLE OUTPUT]
Public Sub New(ByVal filename As String)
Console.WriteLine("Constructing Class - " & filename & vbCrLf)

_filename = filename
_folder = "I:\tmp\"
Console.WriteLine("_filename = " & _filename & vbCrLf)
Console.WriteLine("_folder = " & _folder & vbCrLf)

OpenLogFile()
Console.WriteLine("End Constructing Class" & vbCrLf)
End Sub

'[ADD CONSOLE OUTPUT]
Protected Overrides Sub Finalize()
Console.WriteLine("Finalizing Class" & vbCrLf)
CloseLogFile()

Console.WriteLine("Finalizing MyBass Class" & vbCrLf)
MyBase.Finalize()

Console.WriteLine("End Finalizing Class" & vbCrLf)
End Sub

Private _filename As String = String.Empty
Private _folder As String = String.Empty
Private _logWriter As StreamWriter

Public ReadOnly Property Path() As String
Get
Return _folder & _filename
End Get
End Property
Public ReadOnly Property NewPath() As String
Get
Return _folder & _filename & "_"
End Get
End Property

'[ADD CONSOLE OUTPUT]
Private Sub OpenLogFile()
Console.WriteLine("Enter 'OpenLogFile'" & vbCrLf)

Dim sqlLogFileSize As Long = 0
Try
Console.WriteLine("*** Log File Opened : " & Me.Path)
_logWriter = New StreamWriter(Me.Path)
_logWriter.AutoFlush = True
_logWriter.WriteLine("test")
Catch ex As Exception
Console.WriteLine("***ERROR : " & ex.Message)
End Try

Console.WriteLine("Exit 'OpenLogFile'" & vbCrLf)
End Sub

'[ADD CONSOLE OUTPUT]
Private Sub CloseLogFile()
Console.WriteLine("Enter 'CloseLogFile'" & vbCrLf)

Try

'[ADDED TEST]
If _logWriter Is Nothing Then
Console.WriteLine("_logWriter Already Released")
Console.WriteLine("Exiting Sub" & vbCrLf)
Goto Exit_sub
End If

_logWriter.Close()
_logWriter = Nothing
GC.Collect()
Console.WriteLine("*** Log File Closed : " & Me.Path)

Try

'[CHANGE TO BLOCK IF/END IF]
If IO.File.Exists(Me.NewPath) Then
IO.File.Delete(Me.NewPath)
End If

If Not IO.File.Exists(Me.NewPath) Then
IO.File.Copy(Me.Path, Me.NewPath)
IO.File.Delete(Me.Path)
Console.WriteLine("*** Log File Renamed : " & Me.NewPath)
Else
Console.WriteLine("*** Log File NOT Renamed. Another " & _
"file with the same name already " & _
"exists and could not be deleted.")
End If
Catch ex As Exception
Console.WriteLine("Error: Log File NOT Renamed: {0}", _
ex.Message)
End Try
Catch ex As Exception
Console.WriteLine("Error closing log file: {0}", ex.Message)
End Try

'[ADDED LABEL IN ORDER TO CAPTURE EXIT EVENT]
Exit_Sub:
Console.WriteLine("Exit 'CloseLogFile'" & vbCrLf)
End Sub
End Class
------------------------------------------

I compiled as "vbc /out:test.exe test.vb"


When I ran 'Test.exe' from the command line as written above, I received
the following output:

===========================================
I:\tmp>test
Enter Main

Creating Object

Constructing Class - swt.test

_filename = swt.test

_folder = I:\tmp\

Enter 'OpenLogFile'

*** Log File Opened : I:\tmp\swt.test
Exit 'OpenLogFile'

End Constructing Class

Finalizing Object

Finalizing Class

Enter 'CloseLogFile'

*** Log File Closed : I:\tmp\swt.test
*** Log File Renamed : I:\tmp\swt.test_
Exit 'CloseLogFile'

Finalizing MyBass Class

End Finalizing Class

Releasing Object

Exit Main

Finalizing Class

Enter 'CloseLogFile'

_logWriter Already Released
Exiting Sub

Exit 'CloseLogFile'

Finalizing MyBass Class

End Finalizing Class
===========================================


Note that finalize is called twice - once manually by calling function
ZapThisPuppyNow and again when the 'Main' sub exits.


Here is the output when I run the code with the call to function
'ZapThisPuppyNow' commented out:

===========================================
Enter Main

Creating Object

Constructing Class - swt.test

_filename = swt.test

_folder = I:\tmp\

Enter 'OpenLogFile'

*** Log File Opened : I:\tmp\swt.test
Exit 'OpenLogFile'

End Constructing Class

Releasing Object

Exit Main

Finalizing Class

Enter 'CloseLogFile'

*** Log File Closed : I:\tmp\swt.test
*** Log File Renamed : I:\tmp\swt.test_
Exit 'CloseLogFile'

Finalizing MyBass Class

End Finalizing Class
===========================================


Ralf
 
S

stand__sure

didn't catch your nuance in the original code, but this works without a
hitch

Private Sub CloseLogFile()

Try
_logWriter.Close()
Debug.WriteLine("*** Log File Closed : " & Me.Path)
If IO.File.Exists(Me.NewPath) Then IO.File.Delete(Me.NewPath)
If IO.File.Exists(Me.Path) Then IO.File.Delete(Me.Path)
Catch ex As Exception
Debug.WriteLine("Error closing log file: {0}", ex.Message)
End Try
End Sub

there was an issue specifically with the x=Nothing line when I tested
it the first time (a Null reference exception). I was NOT able to
replicate your specific error, but in hindsight suspect that you had it
open in a viewer to check its contents when the deletion was requested.

as for your original code, I have a great number of issues which I will
summarize as "Don't do it this way!"
1) Close the file as soon as you are done with it! If you need back
into it, re-open it.
2) Use Try...Catch...Finally.
3) Test to make sure that the file actualy opened, before closing it.
4) Combine _folder and _filename into one member variable and access it
directly
5) Don't use property calls within the class; you already own the
private member fields
6) If the output is for debug, use the Debug class and not the console
 
D

Don

_AnonCoward said:
I can't reproduce this.

My original code worked fine on your computer? That's odd....

I copied your code into a console app with a few
changes. I removed your original comments and added new comments to flag
my changes (highlighted below). I only made two changes of any note:

* I added a new function 'ZapThisPuppyNow' to the class so I could
force a finalize event.

Yes, I know that explicitly calling a method to close the file before
destroying the object will make everything work fine. Unfortunately it
defeats the purpose of what I wanted to do: have the opening and closing of
the file be automatic without having to make any special method calls to
either open or close the file. Unfortunately, it looks like there's not
other way around this bug. :(

- Don
 
D

Don

Thanks for the tips, but it looks like this is a lost cause. I guess I'll
just have to go to Plan B where I have to manually call methods to open and
close the log file. Too bad.

- Don
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top