我可以覆盖C#中已锁定()的对象吗?

时间:2011-01-16 04:39:03

标签: c# .net multithreading

我有一些我想发送到服务器的对象,但我想确保这是将数据从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>();
            }
        }
      }

2 个答案:

答案 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,而另一个线程仍在您认为的锁定区域内。

你应该:

  1. lock关于另一个实例变量(最好是readonly个),甚至可能是一个Object实例,它纯粹是被锁定的对象。
  2. 修改算法,使其不会创建List<>的新实例。例如,如果你要做的就是清空列表,只需拨打StagingQueue.Clear()