我有一些代码在针对 .Net 4.5.2 的单线程进程中运行。此代码创建一个文件,执行一些操作,然后删除该文件。大多数情况下,这工作得很好,但有时会抛出异常。
我已尝试简化并重现以下问题。从屏幕截图中可以看出,它设法创建和删除文件 240 多次,然后因 UnauthorizedAccessException 而失败。有时下面的代码运行没有错误,有时它会在循环中走得更远。那么这里发生了什么?
在生产环境中,文件在网络共享中写入和删除,当使用网络共享时,IOException 如下抛出(所以它在一个稍微不同的地方中断,在 File.Delete 而不是 ):
System.IO.IOException: The process cannot access the file '\\network_location\sub_dir\TMP1.sql' because it is being used by another process.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.File.InternalDelete(String path, Boolean checkHost)
据我了解,using 语句应该调用 dispose,导致文件流被刷新;所以在 using 语句之后,我希望所有数据都写入磁盘。
我怎样才能确保它按照我的预期始终如一地工作?
static void Main(string[] args)
{
string networkFilePath = @"C:\Temp";
string filename1 = "tmp1.sql";
string filename2 = "tmp2.sql";
string tempFilename1 = Path.Combine(networkFilePath, filename1);
string tempFilename2 = Path.Combine(networkFilePath, filename2);
int interations = 20000;
Console.WriteLine("Started");
try
{
for (int i = 0; i < interations; i++)
{
using (var s = new StreamWriter(tempFilename1))
{
s.Write("aaa");
}
using (var s = new StreamWriter(tempFilename2))
{
s.Write("aaa");
}
FileCompare(tempFilename1, tempFilename2);
File.Delete(tempFilename1);
File.Delete(tempFilename2);
}
}
catch (IOException e)
{
Console.WriteLine(e.Message);
}
Console.WriteLine("Finished");
Console.ReadLine();
}
public static bool FileCompare(string filename1, string filename2)
{
using (var fs1 = new FileStream(filename1, FileMode.Open))
using (var fs2 = new FileStream(filename2, FileMode.Open))
{
return [... ommited for brevity, but just checks the content of file1 vs file2]
}
}
编辑 我知道我可以使用临时文件名来避免这个问题……问题是,我如何确定 IO 操作已完成,例如,缓存的数据是在本地还是在网络上写入磁盘?
最佳答案
大多数情况下,当某些其他程序使用包括 FileShare.Delete
的宽容共享模式打开同一文件时,就会发生这种情况。此共享允许另一个进程删除文件,但文件不会真正被删除,直到它的所有句柄都关闭(包括由使用 FileShare.Delete
打开它的进程获得的句柄)。此类程序的示例可能是搜索索引器和防病毒软件 - 两者都可能想扫描您创建的新文件,并且都不想锁定文件,因此将以非常宽松的共享模式打开它,包括 FileShare.Delete
。
当您在已使用 FileShare.Delete
打开的文件上调用 File.Delete
时 - 它会无任何错误地返回并且文件将被标记为删除(并且将在所有句柄关闭后删除)。任何后续访问此类“已删除”文件的尝试都将抛出您在示例中观察到的拒绝访问异常。
您可以使用以下代码轻松重现:
static void Main(string[] args) {
var tmpFile = Path.GetTempFileName();
// file is now created
new Thread(() => {
using (var fs = new FileStream(tmpFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) {
// open with permissive share mode, including FileShare.Delete
// and hold that handle for 5 seconds
Thread.Sleep(5000);
}
}).Start();
// wait a bit for thread above to grab a handle
Thread.Sleep(1000);
// delete, will return fine without any errors
File.Delete(tmpFile);
// attempt to create - will fail with access denied exception you have
using (var s = new StreamWriter(tmpFile)) {
s.Write("aaa");
}
Console.WriteLine("done");
Console.ReadLine();
}
因为您不知道另一个进程将持有您要删除的文件的句柄多长时间 - 我认为您所能做的就是检查文件在删除后是否仍然存在,如果是 - 稍等一下,然后再次检查:
File.Delete(tmpFile);
while (File.Exists(tmpFile)) {
// obviously don't loop forever
// check for some time then throw exception
Thread.Sleep(100);
}
// in the example above it will loop for 4 seconds while
// handle is hold by another thread, then finally return
// and subsequent creation of new file will work fine without exceptions
关于c# - 快速创建和删除文件导致异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48098557/