我正在开发一个Web应用程序,其中有几个用户可以更新相同的记录。因此,如果用户同时更新同一记录,以避免出现问题,我将其更改保存在队列中。当每次保存发生时,我想调用一个在另一个线程上处理队列的方法,但我需要确保该方法在另一个线程中再次调用时不能运行。我已经阅读了几篇关于这个主题的帖子,但不确定什么对我的情况最好。以下是我现在的代码。这是处理它的正确方法吗?
public static class Queue {
static volatile bool isProcessing;
static volatile object locker = new Object();
public static void Process() {
lock (locker) {
if (!isProcessing) {
isProcessing = true;
//Process Queue...
isProcessing = false;
}
}
}
}
答案 0 :(得分:13)
新答案
如果要将这些记录持久保存到数据库(或数据文件或类似的持久性系统),则应让该底层系统处理同步。由于JohnSaunders pointed out数据库已经处理了同步更新。
鉴于您希望保留记录... problem presented by John是您只是在Web应用程序的单个实例中同步对数据的访问。尽管如此,可能会有多个实例同时运行(例如,在服务器场中,如果您拥有高流量,这可能是一个好主意)。在这种情况下,使用队列防止同时写入不够好因为网页的多个实例之间仍然存在竞争条件。
在这种情况下,当你从不同的实例获得相同记录的更新时,底层系统仍然必须处理冲突,但它无法可靠地执行,因为更新的顺序已经丢失
除了这个问题,如果您将此数据结构用作缓存,那么它将提供不正确的数据,因为它不知道在另一个实例中发生的更新。
据说,对于可能值得使用线程安全队列的场景。对于这些情况,您可以使用ConcurrentQueue(正如我在原始答案的最后提到的那样)。
我会保留原来的答案,因为我看到了帮助理解.NET中可用的线程同步机制的价值(我预设了一些)。
原始回答
lock
足以阻止多个线程同时访问代码段(这是互斥)。
我在这里评论了你不需要的东西:
public static class Queue {
// static volatile bool isProcessing;
static volatile object locker = new Object();
public static void Process() {
lock (locker) {
// if (!isProcessing) {
// isProcessing = true;
//Process Queue...
// isProcessing = false;
// }
}
}
}
话虽如此,试图进入lock
的所有线程都将在队列中等待。据我所知,这不是你想要的。相反,您希望所有其他线程跳过该块,只留下一个完成工作。这可以通过Monitor.TryEnter:
public static class Queue
{
static volatile object locker = new Object();
public static void Process()
{
bool lockWasTaken = false;
try
{
if (Monitor.TryEnter(locker))
{
lockWasTaken = true;
//Process Queue...
}
}
finally
{
if (lockWasTaken)
{
Monitor.Exit(locker);
}
}
}
}
另一个好的选择是使用Interlocked:
public static class Queue
{
static int status = 0;
public static void Process()
{
bool lockWasTaken = false;
try
{
lockWasTaken = Interlocked.CompareExchange(ref status, 1, 0) == 0;
if (lockWasTaken)
{
//Process Queue...
}
}
finally
{
if (lockWasTaken)
{
Thread.VolatileWrite(ref status, 0);
}
}
}
}
无论如何,您无需实现自己的线程安全队列。您可以使用ConcurrentQueue。