使用 FileStream 删除后创建文件时出现 UnauthorizedAccessException
UnauthorizedAccessException on creating file after deletion using FileStream
我在客户端系统上遇到问题。在尝试在示例代码中重现它时,我已经重现了它。
这是示例代码
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
只需 运行 此代码并单击按钮 3-4 次并注意异常,如下面的屏幕截图所示:
此异常的堆栈跟踪是:
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:
任何人都可以告诉我这个 UnauthorizedAccessException 异常的原因,因为在创建新文件之前文件已经被删除,以及如何解决这个问题?
当您调用 File.Delete(path)
时,您必须给 CPU 一些时间来完成删除文件,然后再返回执行任何可能与相同文件名有关的代码刚删的
有几种方法可以完成同样的事情。这是我的做法:
...
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
....
这很正常,您正在与计算机上 运行 也对该文件感兴趣的其他进程进行斗争。更广为人知的是 "anti-malware" 和 "search indexer"。 "click 3 times" 场景只是您因其他进程花一些时间查看文件内容而引发的故障模式。
此类进程将打开文件进行删除共享,以尽量减少其影响。 .NET 中公开了相同的功能,FileShare.Delete option。这在某种程度上是可行的,您发现删除文件时没有任何问题。但是在其他进程关闭文件句柄之前,文件实际上不会从文件系统中消失。虽然它仍然存在,但任何试图打开或覆盖待删除文件的进程都将被打上 "access denied" 错误。
一般来说,您永远不会想使用您现在使用的方法,即删除文件然后尝试重新创建它。考虑到这可能会失败的可能性很大,您将让用户根本没有文件。那是无法恢复的数据丢失。另一种方法是 swap 文件,由 File.Replace() 方法支持。这适用于锁定的文件,这些进程只锁定文件数据,而不锁定目录条目。换句话说,您可以 重命名 文件而不会有任何问题。
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
这仍然不完美,但数据丢失的可能性被消除了,你给了持有文件的进程更多的时间来完成使用它。是否要围绕交换操作放置重试循环取决于您。
从技术上讲,它是可以做到完美的,您必须给备份文件名一个随机名称,这样您下次创建文件时就永远不会删除它。然而,这会将文件喷射到难以删除的磁盘上。如果执行此操作,则还必须将文件移至驱动器的回收站,以便它在将来 天后被删除。幸运的是,这在 VB.NET 中很容易做到,使用 DeleteFile() 辅助函数并指定 RecycleOption.SendToRecycleBin。但仅当您保存到本地驱动器时才适用。
我在客户端系统上遇到问题。在尝试在示例代码中重现它时,我已经重现了它。
这是示例代码
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
只需 运行 此代码并单击按钮 3-4 次并注意异常,如下面的屏幕截图所示:
此异常的堆栈跟踪是:
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:
任何人都可以告诉我这个 UnauthorizedAccessException 异常的原因,因为在创建新文件之前文件已经被删除,以及如何解决这个问题?
当您调用 File.Delete(path)
时,您必须给 CPU 一些时间来完成删除文件,然后再返回执行任何可能与相同文件名有关的代码刚删的
有几种方法可以完成同样的事情。这是我的做法:
...
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
....
这很正常,您正在与计算机上 运行 也对该文件感兴趣的其他进程进行斗争。更广为人知的是 "anti-malware" 和 "search indexer"。 "click 3 times" 场景只是您因其他进程花一些时间查看文件内容而引发的故障模式。
此类进程将打开文件进行删除共享,以尽量减少其影响。 .NET 中公开了相同的功能,FileShare.Delete option。这在某种程度上是可行的,您发现删除文件时没有任何问题。但是在其他进程关闭文件句柄之前,文件实际上不会从文件系统中消失。虽然它仍然存在,但任何试图打开或覆盖待删除文件的进程都将被打上 "access denied" 错误。
一般来说,您永远不会想使用您现在使用的方法,即删除文件然后尝试重新创建它。考虑到这可能会失败的可能性很大,您将让用户根本没有文件。那是无法恢复的数据丢失。另一种方法是 swap 文件,由 File.Replace() 方法支持。这适用于锁定的文件,这些进程只锁定文件数据,而不锁定目录条目。换句话说,您可以 重命名 文件而不会有任何问题。
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
这仍然不完美,但数据丢失的可能性被消除了,你给了持有文件的进程更多的时间来完成使用它。是否要围绕交换操作放置重试循环取决于您。
从技术上讲,它是可以做到完美的,您必须给备份文件名一个随机名称,这样您下次创建文件时就永远不会删除它。然而,这会将文件喷射到难以删除的磁盘上。如果执行此操作,则还必须将文件移至驱动器的回收站,以便它在将来 天后被删除。幸运的是,这在 VB.NET 中很容易做到,使用 DeleteFile() 辅助函数并指定 RecycleOption.SendToRecycleBin。但仅当您保存到本地驱动器时才适用。