簡體   English   中英

在lock()中執行foreach時修改列表

[英]modify a list while doing foreach inside lock()

atm我這樣做是這樣的:

lock (LockObj)
 {
     foreach (var o in Oo)
     {
        var val = o.DateActive;
        if (val.AddSeconds(30) < DateTime.Now) Oo.Remove(o);
     }
}

我得到這個錯誤:

集合已修改; 枚舉操作可能無法執行

應該怎么做?

您必須使用常規的for循環。

for (int i = 0; i < Oo.Length; ++i)
{
    var val = Oo[i];
    if (val.AddSeconds(30) < DateTime.Now)
    {
        Oo.RemoveAt(i);
        i--; // since we just removed an element
    }
}

您無法使用foreach循環編輯集合的原因是因為foreach使用您要迭代的集合的只讀IEnumerator

您無法修改您要枚舉的集合。要更改它,請獲取它的副本並進行更改。

for(var k in OO.ToList())
.....

要么

使用count並使用索引迭代集合,

for (int i=0;i<OO.Count();i++)
.....

問題與鎖無關。

使用for()循環而不是foreach()。

我無法100%替換您的代碼,因為您的代碼沒有提示“ Oo”是什么集合類型。 名稱“ Oo”也沒有。 也許var關鍵字過度使用的弊端之一? 也許我只是看不到您的代碼太多;)

int size = Oo.Length();
for(int i = 0; i < size; i++){
    if (Oo[i].AddSeconds(30) < DateTime.Now){
        Oo[i].RemoveAt(i);
        size--; // Compensate for new size after removal.
    }
}

如果要使用foreach進行迭代,則根本無法修改集合。 您有兩個選擇,使用For而不是foreach循環,或創建另一個Collection並進行修改。

此問題與鎖定完全無關。

如果從List添加/刪除元素,則指向該List所有迭代器都將變為無效。

使用迭代器的一種替代方法是手動處理索引。 然后,您可以向后迭代並使用RemoveAt刪除元素。

for(int i=Oo.Count-1;i>=0;i--)
{
  var o=Oo[i];
  if (o.DateActive.AddSeconds(30)<DateTime.Now)
    Oo.RemoveAt(i);
}

不幸的是,這個本地實現是O(n^2) 如果以更復雜的方式編寫代碼,則首先將元素分配給它們的新位置,然后截斷列表,則它變為O(n)

如果OoList<T>那么有更好的解決方案。 您可以使用Oo.RemoveAll(o=>o.DateActive.AddSeconds(30)<DateTime.Now) 不幸的是,默認情況下, IList<T>上沒有此類擴展方法。

我會這樣寫代碼:

lock (LockObj)
 {
     DateTime deleteTime=DateTime.Now.AddSeconds(-30);
     Oo.RemoveAll(o=>o.DateActive<deleteTime);
}

作為旁注,我個人將UTC時間而不是本地時間用於此類代碼。

class Program 
{
    static void Main(string[] args)
    {
        List<OOItem> oo = new List<OOItem>();

        oo.Add( new OOItem() { DateActive = DateTime.Now.AddSeconds(-31) });

        lock(LockObj)
        {
              foreach( var item in oo.Where( ooItem => ooItem.DateActive.AddSeconds(30) < DateTime.Now  ).ToArray())
              {
                 oo.Remove(item);
              } 
        }

        Debug.Assert( oo.Count ==  0);
    }
}

public class OOItem
{
    public DateTime DateActive { get; set; }
}

我將建議一種避免弄亂循環索引和其他使代碼難以理解的東西的方法。

我認為最好的選擇是編寫一個不錯的查詢,然后對將查詢轉換為數組的結果進行一次foreach

var inactives = from o in Oo
                where o.DateActive < DateTime.Now
                select o;

foreach (var o in inactives.ToArray())
{
    Oo.Remove(o);
}

這避免了集合更改的問題,並使代碼更具可讀性。

如果您更加注重“功能”,那么這里是另一種選擇:

(from o in Oo
 where o.DateActive < DateTime.Now
 select o)
     .ToList()
     .ForEach(o => Oo.Remove(o));

請享用!

您可以使用Parallel.ForEach(oO,val => {oO.Remove(val);})

並行沒有IEnumerator問題!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM