我有一个 Web 应用程序,它控制哪些 Web 应用程序从我们的负载均衡器获取流量。Web 应用程序在每个单独的服务器上运行。
它跟踪处于 ASP.NET 应用程序状态的对象中每个应用程序的“输入或输出”状态,并且只要状态发生更改,该对象就会被序列化为磁盘上的一个文件。当 Web 应用程序启动时,状态会从文件中反序列化。
虽然该站点本身只收到几个请求,而且它很少访问该文件,但我发现由于某种原因在尝试读取或写入文件时很容易发生冲突。这种机制需要非常可靠,因为我们有一个自动化系统,定期对服务器进行滚动部署。
在任何人发表任何质疑上述任何谨慎性的评论之前,请允许我简单地说,解释其背后的原因会使这篇文章比现在长得多,所以我想避免搬山。
也就是说,我用来控制对文件的访问的代码如下所示:
internal static Mutex _lock = null;
/// <summary>Executes the specified <see cref="Func{FileStream, Object}" /> delegate on
/// the filesystem copy of the <see cref="ServerState" />.
/// The work done on the file is wrapped in a lock statement to ensure there are no
/// locking collisions caused by attempting to save and load the file simultaneously
/// from separate requests.
/// </summary>
/// <param name="action">The logic to be executed on the
/// <see cref="ServerState" /> file.</param>
/// <returns>An object containing any result data returned by <param name="func" />.
///</returns>
private static Boolean InvokeOnFile(Func<FileStream, Object> func, out Object result)
{
var l = new Logger();
if (ServerState._lock.WaitOne(1500, false))
{
l.LogInformation( "Got lock to read/write file-based server state."
, (Int32)VipEvent.GotStateLock);
var fileStream = File.Open( ServerState.PATH, FileMode.OpenOrCreate
, FileAccess.ReadWrite, FileShare.None);
result = func.Invoke(fileStream);
fileStream.Close();
fileStream.Dispose();
fileStream = null;
ServerState._lock.ReleaseMutex();
l.LogInformation( "Released state file lock."
, (Int32)VipEvent.ReleasedStateLock);
return true;
}
else
{
l.LogWarning( "Could not get a lock to access the file-based server state."
, (Int32)VipEvent.CouldNotGetStateLock);
result = null;
return false;
}
}
这通常有效,但有时我无法访问互斥锁(我在日志中看到“无法获得锁定”事件)。我无法在本地重现这个 - 它只发生在我的生产服务器(Win Server 2k3/IIS 6)上。如果我删除超时,应用程序将无限期挂起(竞争条件??),包括后续请求。
当我确实收到错误时,查看事件日志告诉我,在记录错误之前,前一个请求已实现并释放了互斥锁。
互斥体在 Application_Start 事件中实例化。在声明中静态实例化它时,我得到相同的结果。
借口,借口:线程/锁定不是我的强项,因为我通常不必担心它。
关于为什么它随机无法获得信号的任何建议?
更新:
我已经添加了正确的错误处理(多么令人尴尬!),但我仍然遇到相同的错误 - 并且为了记录,未处理的异常从来都不是问题。
只有一个进程会访问该文件——我没有为此应用程序的网络池使用网络花园,也没有其他应用程序使用该文件。我能想到的唯一例外是当应用程序池回收时,旧的 WP 在创建新的 WP 时仍处于打开状态 - 但我可以从任务管理器中看出问题发生在只有一个工作进程时。
@mmr:使用 Monitor 与使用 Mutex 有何不同?根据 MSDN 文档,它看起来好像在有效地做同样的事情——如果我不能用我的 Mutex 获得锁,它会通过返回 false 来优雅地失败。
另一件需要注意的事情:我遇到的问题似乎完全是随机的 - 如果它在一个请求上失败,它可能会在下一个请求上正常工作。似乎也没有模式(至少肯定没有其他模式)。
更新 2:
此锁不用于任何其他调用。在 InvokeOnFile 方法之外引用 _lock 的唯一时间是在实例化它时。
调用的 Func 要么从文件中读取并反序列化为对象,要么将对象序列化并将其写入文件。这两个操作都不是在单独的线程上完成的。
ServerState.PATH 是一个静态只读字段,我不希望它会导致任何并发问题。
我还想重申我之前的观点,即我无法在本地(在 Cassini 中)重现这一点。
得到教训:
- 使用正确的错误处理(呃!)
- 为工作使用正确的工具(并对工具的作用/方式有基本的了解)。正如 sambo 指出的那样,使用 Mutex 显然有很多开销,这导致我的应用程序出现问题,而 Monitor 是专门为 .NET 设计的。