Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/asp.net/30.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何同步对列表的访问<;T>;在ASP.NET中使用?_C#_Asp.net_.net_Multithreading_Access Synchronization - Fatal编程技术网

C# 如何同步对列表的访问<;T>;在ASP.NET中使用?

C# 如何同步对列表的访问<;T>;在ASP.NET中使用?,c#,asp.net,.net,multithreading,access-synchronization,C#,Asp.net,.net,Multithreading,Access Synchronization,我在一个同时访问列表的站点上遇到了一些问题。此列表保存了一个购物车中的项目,多次删除会使网站崩溃。 同步它们的最佳方法是什么? 锁够了吗? 锁选项看起来很难看,因为代码到处都是,而且相当混乱 更新: 这是一个如下实现的列表: 公共类MyList:List{} 这是一个遗留站点,因此不允许对其进行太多修改。 我应该如何重构它,以便在迭代时安全地锁定它 任何想法 使用lock是同步访问泛型集合的正确方法。对集合的访问遍布各地,您可以创建一个包装类来包装对集合的访问,从而减少交互面。然后,您可以在包装

我在一个同时访问列表的站点上遇到了一些问题。此列表保存了一个购物车中的项目,多次删除会使网站崩溃。 同步它们的最佳方法是什么? 锁够了吗? 锁选项看起来很难看,因为代码到处都是,而且相当混乱

更新: 这是一个如下实现的列表: 公共类MyList:List{}

这是一个遗留站点,因此不允许对其进行太多修改。 我应该如何重构它,以便在迭代时安全地锁定它


任何想法

使用lock是同步访问泛型集合的正确方法。对集合的访问遍布各地,您可以创建一个包装类来包装对集合的访问,从而减少交互面。然后,您可以在包装对象内部引入同步。

那么这是在请求之间共享的内存列表吗?这听起来像是一个潜在的问题原因,即使你已经锁定。通常应避免可变的共享集合,例如IME


请注意,如果您确实决定同步,则需要在迭代列表和其他复合操作的整个过程中锁定列表。仅仅锁定每个特定的访问是不够的。

是的,您必须锁定-使用如下方式:

lock (myList.SyncRoot) 
{
    // Access the collection.
}

@Samuel这正是问题的关键:您不能仅仅通过修改现有类来纠正这样的问题。无论如何,
类扩展了List
,这与微软的方式一致,主要是避免使用虚拟方法(只有在微软希望我们重写的地方,它们才会使其虚拟化,这通常是有意义的,但在你需要黑客的情况下会使生活更加困难)。即使它嵌入了
列表
,并且所有对它的访问都通过您可以更改的代码进行,您也不能简单地修改此代码并添加锁来解决问题

为什么??首先,迭代器。不能仅在每次访问集合之间修改集合。在整个枚举过程中不能修改它

实际上,同步是一个复杂的问题,它取决于列表实际上是什么,以及在系统中需要始终是什么样的东西

举例来说,这里有一个滥用IDisposable的黑客。这将嵌入列表并重新声明在某处使用的列表的任何功能,以便可以同步对列表的访问。此外,它不实现IEnumerable。相反,通过列表访问enumerate的唯一方法是通过返回一次性类型的方法。此类型在创建时进入监视器,在释放时再次退出。这确保了列表在迭代期间不可访问。然而,正如我的示例所示,这仍然是不够的

首先是黑客名单:


公共类MyCollection
{
对象syncRoot=新对象();
列表=新列表()

public void Add(T项){lock(syncRoot)list.Add(项);}
公共整数计数
{
获取{lock(syncRoot)返回列表.Count;}
}
公共迭代器包装器GetIteratorWrapper()
{
返回新的迭代器包装器(this);
}
公共类迭代器包装器:IDisposable,IEnumerable
{
布尔处理;
支原体采集c;
公共迭代器包装器(MyCollection c){this.c=c;Monitor.Enter(c.syncRoot);}
public void Dispose(){if(!disposed)Monitor.Exit(c.syncRoot);disposed=true;}
公共IEnumerator GetEnumerator()
{
返回c.list.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
返回GetEnumerator();
}
}
}

然后是使用它的控制台应用程序:


班级计划
{
静态MyCollection字符串=新的MyCollection()

static void Main(字符串[]args)
{
新线程(加法器).Start();
睡眠(15);
dump();
睡眠(125);
dump();
控制台。WriteLine(“按任意键”);
Console.ReadKey(true);
}
静态空转储()
{
WriteLine(string.Format(“Count={0}”,strings.Count).PadLeft(40',-);
使用(var enumerable=strings.GetIteratorWrapper())
{
foreach(可枚举中的var s)
控制台。写入线(s);
}
控制台写入线(“.PadLeft(40’,-”);
}
静态无效加法器()
{
对于(int i=0;i<100;i++)
{
strings.Add(Guid.NewGuid().ToString(“N”);
睡眠(7);
}
}
}

请注意“dump”方法:它是access Count,它无意义地锁定列表以使其“线程安全”,然后遍历这些项。但是Count getter(锁定、获取计数,然后释放)和using语句之间存在竞争条件。因此,它可能不会转储它所做的事情的数量

在这里,这可能无关紧要。但是,如果代码改为执行以下操作呢:

var a = new string[strings.Count];
for (int i=0; i < strings.Count; i++) { ... }
var a=新字符串[strings.Count];
对于(inti=0;i
或者更可能把事情搞砸:

var n = strings.Count;
var a = new string[n];
for (int i=0; i < n; i++) { ... }
var n=strings.Count;
var a=新字符串[n];
对于(int i=0;i
如果同时将项目添加到列表中,前者将崩溃。如果将项目从列表中删除,则后者的效果不佳。在这两种情况下,代码的语义可能不会不受列表更改的影响,即使更改不会导致代码崩溃。在第一种情况下,可能会从列表中删除项,从而导致数组无法填充。随后,由于数组中的空值,代码中相距甚远的内容崩溃

从中得到的教训是:共享状态可能非常棘手。你需要一个预先的计划,你需要有一个策略来确保你的意思是正确的

在上述每种情况下,只有确保锁跨越所有区域,才能实现正确的操作
var a = new string[strings.Count];
for (int i=0; i < strings.Count; i++) { ... }
var n = strings.Count;
var a = new string[n];
for (int i=0; i < n; i++) { ... }