问题
I am facing an issue on client system. On trying to reproduce it in a sample code I have reproduced it.
Here's the sample code
Imports System.IO
Public Class Form1
Private _lock As New Object
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim t As New Threading.Thread(AddressOf createFile)
With t
.IsBackground = True
.Name = Guid.NewGuid.ToString
.Start()
End With
End Sub
Private Sub createFile()
Dim path As String = "D:\SomeFile.txt"
For i As Integer = 0 To 1000
SyncLock _lock
If File.Exists(path) Then File.Delete(path)
Using fs As New FileStream(path, FileMode.CreateNew)
End Using
End SyncLock
Next
End Sub
End Class
Just Run this code and click the button 3-4 times and notice the exception as shown in screenshot below:
The stacktrace of this exception is:
System.UnauthorizedAccessException was unhandled Message=Access to the path 'D:\SomeFile.txt' is denied. Source=mscorlib StackTrace: at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath) at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy) at System.IO.FileStream..ctor(String path, FileMode mode) at WindowsApplication1.Form1.createFile() in C:\Users\premjeet.singh\Desktop\WindowsApplication1\WindowsApplication1\Form1.vb:line 23 at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.runTryCode(Object userData) at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() InnerException:
Can anybody let me know the reason for this UnauthorizedAccessException exception as the file is already deleted before creating the new one and how it can be solved?
回答1:
This is pretty normal, you are doing battle with other processes that run on your machine that are also interested in the file. Better known as "anti-malware" and "search indexer". The "click 3 times" scenario is just the failure-mode you induce by those other processes taking a while to have a look-see at the file content.
Such processes will open the file for delete sharing to minimize their impact. Same capability is exposed in .NET, FileShare.Delete option. That works, to a degree, you don't have any trouble deleting the file as you found out. But the file will not actually disappear from the file system until those other processes close their handle on the file. While it still exists, any process that tries to open or overwrite the pending-delete file will be slapped with an "access denied" error.
In general, you never want to use the approach you use now, deleting the file and then trying to recreate it. Given the considerable odds that this can fail, you'll leave the user with no file at all. That's irrecoverable loss of data. The alternative is to swap the file, supported by the File.Replace() method. Which works well for locked files, these processes only have a lock on the file data, not on the directory entry. In other words, you can rename the file without trouble.
Private Function createFile(ByVal outpath As String) As Boolean
Dim temp As String = Path.Combine(Path.GetDirectoryName(outpath), Guid.NewGuid.ToString())
Dim bak As String = outpath + ".bak"
'' Create the file first
Using fs As New FileStream(temp, FileMode.CreateNew)
''...
End Using
'' Now try to swap it in place
Try
File.Delete(bak)
File.Replace(temp, outpath, bak)
Catch
File.Delete(temp)
Return False
End Try
'' That worked, don't need the backup anymore. Failure to delete is not fatal
Try
File.Delete(bak)
Catch
End Try
Return True
End Function
This still isn't perfect but the odds for data loss are eliminated and you give the process that is holding on the file more time to finish using it. Whether you want to put a retry-loop around the swap operation is up to you.
Technically it is possible to make it perfect, you have to give the backup filename a random name so you can never fail to delete it the next time you create the file. That however sprays files to the disk that are hard to get rid of. If you do this then you must also move the file to the drive's Recycle Bin so it will get deleted some day in the future. Luckily that's easy to do in VB.NET, use the DeleteFile() helper function and specify RecycleOption.SendToRecycleBin. But only appropriate if you save to a local drive.
回答2:
When you invoke File.Delete(path)
, you must give the CPU some time to finish deleting the file before coming back to execute any further code that might have anything to do with the same file name as the one just deleted.
Several ways can accomplish the same thing. Here is how I would do it:
...
If File.Exists(path) Then
File.Delete(path)
Application.DoEvents() 'Force completing any pending events
'immediately start deleting the file
System.Threading.Thread.Sleep(1000) 'optional: wait 1 second
While System.IO.File.Exists(path) 'In case the file is still in
' the process of being deleted
' Wait here for it to finish
End While
Using fs As New FileStream(path, FileMode.CreateNew) 'Now create the file
End If
....
来源:https://stackoverflow.com/questions/28405934/unauthorizedaccessexception-on-creating-file-after-deletion-using-filestream