我正在编写一个Asp.Net Core应用程序(带有RazerPages),用于上载/下载文件。我有一个使用AJAX上载文件的控件。文件将上传到服务器上的子目录。子目录的名称是页面加载时生成的Guid。
当我从控件中删除文件时,它会发送一条命令来删除服务器上的关联文件。问题在于,对于特别大的文件,删除似乎需要很长时间,但GUI不在等待响应(因此认为文件已被删除)。如果然后我尝试再次上传相同的文件,则会收到“访问被拒绝”异常,因为该文件仍被另一个请求使用...
无论何时发生文件IO,我都试图使用互斥锁来锁定子目录,但是由于某些原因,不同的请求似乎并没有使用相同的互斥锁。如果我使用静态单例互斥量,则可以使用,但是这意味着整个服务器一次只能上传/删除一个文件。
如何为当前正在使用的子目录创建互斥锁,并在多个请求中识别它?
public class FileIOService : IFileService
{
public string RootDiectory { get; set; }
public void CreateDirectory(Guid id)
{
// Create the working directory if it doesn't exist
string path = Path.Combine(RootDiectory, id.ToString());
lock (id.ToString())
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
}
public void AppendToFile(Guid id, string fileName, Stream content)
{
try
{
CreateDirectory(id);
string fullPath = Path.Combine(RootDiectory, id.ToString(), fileName);
lock (id.ToString())
{
bool newFile = !File.Exists(fullPath);
using (FileStream stream = new FileStream(fullPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
{
using (content)
{
content.CopyTo(stream);
}
}
}
}
catch (IOException ex)
{
throw;
}
}
public void DeleteFile(Guid id, string fileName)
{
string path = Path.Combine(RootDiectory, id.ToString(), fileName);
lock (id.ToString())
{
if (File.Exists(path))
{
File.Delete(path);
}
string dirPath = Path.Combine(RootDiectory, id.ToString());
DirectoryInfo dir = new DirectoryInfo(dirPath);
if (dir.Exists && !dir.GetFiles().Any())
{
Directory.Delete(dirPath, false);
}
}
}
public void DeleteDirectory(Guid id)
{
string path = Path.Combine(RootDiectory, id.ToString());
lock (id.ToString())
{
if (Directory.Exists(path))
{
Directory.Delete(path, true);
}
}
}
}
答案 0 :(得分:0)
我最终没有使用lock
,而是使用了显式创建的全局互斥锁。我创建了一个专用的ProtectWithMutex
方法,该方法将在受保护的代码块内执行操作:
/// <summary>
/// Performs the specified action. Only one action with the same Guid may be executing
/// at a time to prevent race-conditions.
/// </summary>
private void ProtectWithMutex(Guid id, Action action)
{
// unique id for global mutex - Global prefix means it is global to the machine
string mutexId = string.Format("Global\\{{{0}}}" ,id);
using (var mutex = new Mutex(false, mutexId, out bool isNew))
{
var hasHandle = false;
try
{
try
{
//change the timeout here if desired.
int timeout = Timeout.Infinite;
hasHandle = mutex.WaitOne(timeout, false);
if (!hasHandle)
{
throw new TimeoutException("A timeout occured waiting for file to become available");
}
}
catch (AbandonedMutexException)
{
hasHandle = true;
}
TryAndRetry(action);
}
finally
{
if (hasHandle)
mutex.ReleaseMutex();
}
}
}
这阻止了多个请求尝试同时操作同一目录,但是我仍然遇到其他进程(Windows Explorer,Antivirus,我不太确定)在两次请求之间抓取文件的问题。为了解决这个问题,我创建了一个TryAndRetry
方法,该方法将尝试一遍又一遍地执行相同的操作,直到成功(或者直到失败太多次为止)为止:
/// <summary>
/// Trys to perform the specified action a number of times before giving up.
/// </summary>
private void TryAndRetry(Action action)
{
int failedAttempts = 0;
while (true)
{
try
{
action();
break;
}
catch (IOException ex)
{
if (++failedAttempts > RetryCount)
{
throw;
}
Thread.Sleep(RetryInterval);
}
}
}
这时我要做的就是用对我的lock
方法的调用替换我所有的Protected
块:
public void AppendToFile(Guid id, string fileName, Stream content)
{
CreateDirectory(id);
string dirPath = Path.Combine(RootDiectory, id.ToString());
string fullPath = Path.Combine(dirPath, fileName);
ProtectWithMutex(id, () =>
{
using (FileStream stream = new FileStream(fullPath, FileMode.Append, FileAccess.Write, FileShare.None))
{
using (content)
{
content.CopyTo(stream);
}
}
});
}
public void DeleteFile(Guid id, string fileName)
{
string path = Path.Combine(RootDiectory, id.ToString(), fileName);
ProtectWithMutex(id, () =>
{
if (File.Exists(path))
{
File.Delete(path);
}
string dirPath = Path.Combine(RootDiectory, id.ToString());
DirectoryInfo dir = new DirectoryInfo(dirPath);
if (dir.Exists && !dir.GetFiles().Any())
{
Directory.Delete(dirPath, false);
}
});
}