2
   public Response Delete(Request fRequest)
        {
            Response fResponse = new Response();
            try
            {

                    foreach (Record rec in fRequest.Records)
                    {
                        string personid = rec.PersonId;
                        int recordId = rec.RecordId;

                        EnrollRecord record = (from lst in listEnrolledCandidates
                                            where lst.PersonId == personid && lst.RecordId      == recordId
                                            select lst).FirstOrDefault();
                      lock (lockDelete)
                      {
                        listEnrolledCandidates.Remove(record);

                        int aIndex = record.ArraryIndex;  
                        pvTemplatesArrayList.RemoveAt(aIndex)                       

                      }
                    }

                    return fResponse;

            }
            catch (Exception ex)
            {


                return null;
            }

        }

在上面的源代码中,锁被保留在 Remove 语句中。这意味着一次只有一个线程可以执行这三行代码。现在,如果一个线程(例如“A”)正在执行这 3 行,而另一个线程(例如“B”)使用另一个 Request 参数调用“Delete”方法,线程 A 是否有可能在执行时获取 EnrollRecord、personid 和recordid 的更新值作为 Request 对象的操作不同?

4

3 回答 3

1

是的,这似乎是完全可能的,所以EnrollRecord record = (from lst in listEnrolledCandidates where lst.PersonId == personid && lst.RecordId == recordId select lst).FirstOrDefault();必须是关键部分的一部分并防止多线程。

于 2013-03-18T06:27:03.617 回答
1

我不认为'personId'和'recordId'会根据每个线程的执行而改变它们的值(这假设fRequest参数对于每个线程都是唯一的)

但是,“记录”可能会发生奇怪的事情。没有改变值,但是线程 A 获取它的值,然后线程 B 从列表中删除该值,因此它实际上不再存在。

任何“全局”或共享变量都需要在多线程情况下对其访问进行同步。

我会将列表访问权限移到锁内。

于 2013-03-18T06:46:21.273 回答
0
  • 不,线程 B 不会更改线程 A 的recordid andpersonid值`
  • 不,线程 B 不会更改线程 A 对 的引用,但如果线程 B 都引用同一个对象,则record线程 B 可以更新对象的内部状态EnrollRecordEnrollRecord
  • 不过,最重要的是,您的方法可能不是线程安全的:

编辑:

我不允许发表评论,因此在您的其他评论中澄清您的问题:

但是“EnrollRecord 记录”将被视为线程 A 上下文中的本地对象。A 将包含它自己的副本,线程 B 将包含它自己的副本。难道不是

这可能不是这种情况,理解这一点很重要。EnrollRecord,我假设,是一个类。因此它是一个引用类型。每个线程都可以通过 引用同一个对象record,但它的引用变量record在线程之间是不共享的。正如您的代码所代表的那样,每个线程都不会创建EnrollRecord. 例外情况是,例如,您的 linq 查询导致发生 sql 查询,并且您的数据提供者允许在内存中创建对象的重复副本。我假设情况并非如此,因为您正在使用锁来保护您的删除,这表明为listEnrolledCandidates.

结束编辑,继续原始答案:

要使此方法线程安全,您可能还需要使用锁保护以下行:

EnrollRecord record = (from lst in listEnrolledCandidates
                                        where lst.PersonId == personid && lst.RecordId      == recordId
                                        select lst).FirstOrDefault();

为什么?

我将尝试将您的问题分解为各个部分,并逐一回答:

  1. 一次只有一个线程可以执行锁内的 3 行
  2. 线程 A 是否有可能在执行操作时获取以下更新值,因为 Request( fRequest) 对象不同:
    • EnrollRecord
    • personid
    • recordid

因此,我们将从您的陈述中假设“由于 Request ( fRequest) 对象不同”,您的意思是:Request ( fRequest) 对象是单独创建的,因此它不是线程 A 和 B 之间可以共享的引用。

把它放在一起:

一次只有一个线程执行锁定块中的 3 行。

personid我知道这不是一个问题,但最初澄清这一点很重要,因为它有助于理解为什么recordId线程 A 不会被线程 B 更新。

任意数量的线程都可以进入一个方法,但您需要记住该方法访问的对象的范围。您几乎可以将编写的方法描绘为“蓝图”,当线程进入一个方法时,它会从该蓝图创建一个物理构造。每个线程都有自己的物理构造。将蓝图视为汽车的设计。你和我可能驾驶相同品牌、型号、年份、颜色的汽车,但你不会在我开车时坐在我的腿上,当你更换电台时,我的电台不会改变 - 我们每个人都有我们自己的副本。

在您的示例中:

  1. string personid作用域为方法,因此它的值特定于调用此方法的线程。
  2. 这同样适用于int recordId
  3. EnrollRecord record也适用于该方法,因此record您在线程 A 中引用的 不能由record线程 B更新。

但是,record引用类型,不像int值类型。这个非常重要。record是对对象的引用。它被限定在一个方法内。因此,每个线程可能持有对同一对象或不同对象的引用。如果一个线程改变了它的引用,它不会影响另一个线程,但是如果他们改变了引用对象中的某些东西(参见下面的其他点),那么两个线程都会看到这个变化。

如果其中任何一个是类字段而不是方法变量,那么一个线程可以更改另一个线程看到的值/引用。

引用类型与值类型

值类型:如果线程 A 更新变量 recordId,线程 B 将看不到这个。如果 recordId 在类级别被声明为一个字段(例如 private int recordId),而不是在方法级别声明为变量,那么情况就不是这样了——任何线程都可以访问该值并且可以阅读和更新它。要理解为什么会这样,您需要研究堆栈和堆存储之间以及变量范围的区别。带回家:在方法中声明和使用的值类型(大多数情况下)不能被运行相同方法的其他线程访问。

引用类型 - 如果线程 A 将值设置record为带有 (PersonId为 (1, 1) 的 ( ,RecordId,然后线程 B 将其设置为对应于 (2, 2) 的记录,那么线程 A 仍然会引用 (1, 1)。- 如果线程 A 将值设置为 (1, 1) 的 ( , )record记录,然后线程 B 将其设置为 (1, 1) 对应的记录,那么线程 A 仍会引用 (1, 1) , 线程 B 也是如此。PersonIdRecordId

所以,是的,一次只有一个线程可以进入锁定区域,但您不需要锁定来保护本地声明的变量。需要锁来保护共享状态,而不是线程本地的变量。如果不是这种情况,我们将无法从多个线程运行以下方法并获得正确答案:

public int MinusOne(int value) { 
    int newValue = value - 1; 
    return newValue; 
}

你的问题最重要的一点

问题中最重要的部分是你实际上没有问的问题。问题是: 这个方法是线程安全的吗

当我看到您的代码时,答案必须是:

为了回答您的问题,我必须假设这listEnrolledCandidates是对类型列表的共享引用IEnumerable<EnrollRecord>。我还必须假设,因为您使用锁定从该列表中删除记录,所以它不是线程安全的集合/列表/字典。

这很关键:您正在锁定,因此线程 B 无法尝试从listEnrolledCandidates线程 A 删除记录,但在以下情况下会发生什么情况:

情景 1。

  • record线程 A在其范围内设置引用
  • 线程B开始枚举listEnrolledCandidates查找记录
  • 线程A删除记录
  • 线程 B 完成枚举

情景 2。

  • 线程 A 将引用设置为record对应于搜索键 (1, 1)
  • 线程 B 将引用设置为record对应于搜索键 (1, 1)
  • 线程 A 现在删除记录
  • 线程 B 现在删除记录

在这两种情况下,您最终都可能遇到问题。如果您使用的是非线程安全的集合、列表、字典,则必须保护搜索、枚举、循环以及添加、删除、更新等。出于这些目的,您可以积极使用lock,也可以使用 ReaderWriterLock (或相同的苗条版本)。

在第二种情况下,您还想在进入删除锁定区域时再次检查该记录是否仍然存在,以防 Remove 引发record不再在集合中的异常。

其他要点

假设您在EnrollRecord诸如public string EnrollYear { get; set; }. 还假设线程 A 和线程 B 都引用(1 PersonId, RecordId1) 的 ( , )。- 如果线程 A 更改EnrollYearrecord新值,则线程 B 将看到此更改。- 如果线程 A 设置recordnull,那么线程 B 将不会看到记录为null,它仍然会看到保持其对记录的引用

在我们的汽车示例中:如果您调用我正在收听的广播电台(假设 RadioStation 是一个具有 CurrentSong 属性的对象)并请求一首新歌曲,您将更改我正在收听的歌曲。(我想我已经尽可能地采用了这个类比)。

在此示例中,每个线程都拥有对同一对象的单独引用

于 2013-03-18T10:19:52.083 回答