錯誤解說
今天公司的程式出現了一個Bug,錯誤如下:
System.ArgumentException: 目的陣列不夠長。請檢查 destIndex 與長度,以及陣列的下限。
後來發現是下面這段Code的問題:
dataGridView.DataSource = new List<Log>(LogInfos);
因為LogInfos這個List會被其他的執行緒改動,所以在把LogInfos丟進new List<>()裡面觸發Array.Copy到新的List的同時被其他執行緒更動到了,所以才發生錯誤!
解決方法
後來查資料後找到一個不用在程式碼加Lock的方法。
把LogInfos再用一個自定義的物件包起來,如下:
public class Container<T>
{
private ImmutableList<T> _items = ImmutableList<T>.Empty;
private IReadOnlyList<T> Items { get { return _items; } }
public void Add(T item) { _items = _items.Add(item); }
public List<T> ToList()
{
return Items.ToList();
}
}
裡面的資料結構使用ImmutableList,然後要存取資料的時候用IReadOnlyList,把Add跟Get分開,這樣執行緒就不會互相衝突了!
但是這麼做會有幾個缺點:
- 因為
ImmutableList內部是用BinaryTree去實現的,所以Add的時間複雜度不是O(1)而是O(log N)!所以如果會頻繁的Add的話就不建議用。 - 有可能會發生Stale Read,意思就是存取資料的時候,資料並沒有反應到最近一次的更新。這個狀況會發生在
ToList的同時有人Add,這樣雖然不會有Exception,但是讀到的資料不會包括同時新增的那一筆。 所以這個方法要能容許這個狀況的發生。