我有一些我想发送到服务器的对象,但我想确保这是将数据从Stage移动到Upload的唯一线程。以下代码在多线程环境中是否有效?
List<CounterInternal> UploadToServer = new List<CounterInternal>();
List<CounterInternal> StagingQueue = new List<CounterInternal>();
lock (this.UploadToServer)
lock (this.StagingQueue)
{
if (UploadToServer.Count == 0)
{
UploadToServer = StagingQueue.DoDeepCopyExtensionMethod();
// is the following line valid given that I have a Lock() on it?
StagingQueue = new List<CounterInternal>();
}
}
}
答案 0 :(得分:4)
技术上,是的,但这是一个坏主意。考虑一下这个C#源文件:
using System;
class Foo {
static object foo = new object();
static void Main() {
lock (foo) {
foo = new object();
}
}
}
Main()
方法将编译为:
.method private static hidebysig
default void Main () cil managed
{
// Method begins at RVA 0x2100
.entrypoint
// Code size 35 (0x23)
.maxstack 3
.locals init (
object V_0)
IL_0000: ldsfld object Foo::foo
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: call void class [mscorlib]System.Threading.Monitor::Enter(object)
.try { // 0
IL_000c: newobj instance void object::'.ctor'()
IL_0011: stsfld object Foo::foo
IL_0016: leave IL_0022
} // end .try 0
finally { // 0
IL_001b: ldloc.0
IL_001c: call void class [mscorlib]System.Threading.Monitor::Exit(object)
IL_0021: endfinally
} // end handler 0
IL_0022: ret
} // end of method Foo::Main
这对应于以下来源(手工反编译):
static void Main() {
object V_0 = foo;
Monitor.Enter(V_0);
try {
foo = new object();
} finally {
Monitor.Exit(V_0);
}
}
因此锁定的对象将存储在本地 - 这可以保证即使替换存储在字段中的对象引用,也会释放对象的监视器。单独使用此技术不会产生死锁,Monitor.Enter()
上已阻塞的任何其他线程将继续像往常一样阻塞,直到此线程释放锁定。
但是,在之后输入此方法的任何线程你已经重新分配了对象,而在活动线程释放锁之前将获取对 new的锁定对象,因此锁块中可以同时有两个线程。
更好的解决方案是使用单独的对象并锁定它。我通常使用类System.Object
(或只是object
)中的某些东西,因为它所做的只是充当互斥体。这将允许所有线程锁定同一对象,同时允许其他对象引用更改。当您需要锁定以改变无法锁定的值类型时,这也是一种有用的技术。
答案 1 :(得分:0)
这不是一个好主意。 lock
对特定实例进行操作,并在执行new List<>
时创建一个新实例。另一个线程可以出现并锁定新的StagingQueue,而另一个线程仍在您认为的锁定区域内。
你应该:
lock
关于另一个实例变量(最好是readonly
个),甚至可能是一个Object
实例,它纯粹是被锁定的对象。List<>
的新实例。例如,如果你要做的就是清空列表,只需拨打StagingQueue.Clear()
。