C# 如何防止方法跨多个线程运行?
我正在开发一个web应用程序,其中多个用户可以更新相同的记录。因此,为了避免用户同时更新同一记录时出现问题,我将他们的更改保存在队列中。每次保存时,我想调用一个在另一个线程上处理队列的方法,但我需要确保如果再次调用该方法,该方法将无法在另一个线程中运行。我已经读了好几篇关于这个主题的帖子,但不确定什么最适合我的情况。下面是我现在的代码。这是正确的处理方法吗C# 如何防止方法跨多个线程运行?,c#,multithreading,C#,Multithreading,我正在开发一个web应用程序,其中多个用户可以更新相同的记录。因此,为了避免用户同时更新同一记录时出现问题,我将他们的更改保存在队列中。每次保存时,我想调用一个在另一个线程上处理队列的方法,但我需要确保如果再次调用该方法,该方法将无法在另一个线程中运行。我已经读了好几篇关于这个主题的帖子,但不确定什么最适合我的情况。下面是我现在的代码。这是正确的处理方法吗 public static class Queue { static volatile bool isProcessing;
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;
}
}
}
}
新答案 如果要将这些记录持久化到数据库(或数据文件或类似的持久化系统),则应该让底层系统处理同步。因为数据库已经处理同步更新
如果你想保留这些记录。。。问题是,您只同步对web应用程序的单个实例中的数据的访问。不过,可能有多个实例同时运行(例如,在服务器场中,如果流量很大,这可能是个好主意)。在这种情况下,使用队列来防止同时写入是不够的,因为网页的多个实例之间仍然存在竞争条件 在这种情况下,当您从不同实例获取同一记录的更新时,参考底图系统无论如何都必须处理冲突,但由于更新顺序已丢失,因此无法可靠地处理冲突 除此之外,如果您将此数据结构用作缓存,那么它将提供不正确的数据,因为它不知道在另一个实例中发生的更新
尽管如此,对于可能值得使用线程安全队列的场景。对于这些情况,您可以使用(正如我在原始答案末尾提到的) 我将保留我最初的答案,因为我认为有助于理解.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;
// }
}
}
}
话虽如此,所有试图进入
锁的线程都将在队列中等待。据我所知,这不是你想要的。相反,您希望所有其他线程跳过该块,只留下一个线程执行该工作。这可以通过以下方式实现:
另一个好的选择是使用:
无论如何,您不需要实现自己的线程安全队列。您可以使用。-1:在web应用程序中不够好。在回收或web场或web花园的情况下,可能有多个AppDomain副本在运行。@JohnSaunders您是在告诉我多个AppDomain共享静态字段吗?不,我是在告诉您,可能有多个AppDomain同时运行同一代码,所以静态域的多个副本,这意味着多个AppDomain之间没有同步。@johnsa理解是的,因为它不是相同的数据。尝试在这个级别同步它们是没有意义的。如果我们正在处理需要跨AppDomains保存的数据,那么——正如您所建议的那样——使用数据库是一个更好的主意。我不确定这是否是用户2628438想要的。为什么数据会不一样呢?它是同一个web应用程序,可能是同一组用户。数据库知道如何避免同时更新问题。为什么要重新发明轮子?数据存储在Azure表中。在原始记录请求和更新之间,处理可能需要一些时间。如果另一个用户在之后进行了更新,则存储客户端将抛出一个错误。@user2628438我已更新了我的答案以确保中止安全。这意味着,如果具有锁的线程被中止(您无论如何都不应该这样做),另一个线程可以进入。我还修复了第二个示例中的一个bug(如果TryEnter
成功,我必须调用Exit
来完成关键部分)。注意:lock
自C#4.0以来是中止安全的。@Theraot,监视器.TryEnter
示例中的\u syncroot
对象是什么?或者我应该使用那里的locker
对象吗?@user2628438是的,那将是locker
。我已经更新了syncroot
是Monitor
中使用的对象的通用名称,因为当Microsoft认为公开对象是个好主意时,他们就是这样称呼的。您仍然可以找到公开SyncRoot
的类,这些类来自.NET 2.0或更早版本(它是ICollection
接口的一部分,不是泛型接口)。您是否应该使用这些类公开的SyncRoot
?不
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);
}
}
}
}
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);
}
}
}
}