上一期,C#關於List的線程安全問題(一)我們給出了一個線程不安全的例子。
這個例子給人的感覺就是總覺得哪裏不對,命名插入5000個數據到List中,結果卻並不是自己想要的。
明明一共插入了1300個數據,結果也不是。
這都是因爲List默認線程不安全導致的,也就是當某一個線程正在往List中插入數據,結果由於其他線程也正在做插入動作,導致衝突,插入可能失敗,並且插入的順序是不可控的,除非我們並不關心插入的順序。
那麼我們怎麼樣才能讓結果是我們所需要的呢?
這裏我們引入ICollection接口(List<T>是擁有ICollection接口的),這個接口當中有個叫SyncRoot的屬性,它是個輔助屬性,它是微軟實現爲我們準備好了拿來做同步用的,也就是拿來做線程安全用的。它一般用於lock語句塊。用來將集合資源鎖定,可用於同步對ICollection的訪問的對象。
public object SyncRoot { get; }
它是一個實例化的屬性,即每個List實例對象都會有各自的一個SyncRoot。如果想安全訪問集合對象,我們可以如下使用:
ICollection myCollection = someCollection;
lock(myCollection.SyncRoot)
{
// Some operation on the collection, which is now thread safe.
}
所以C#關於List的線程安全問題(一)中的例子,我們可以稍加修改如下:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Collections;
namespace vscode_test2
{
class Program
{
static void Main(string[] args)
{
List<int> mylist = new List<int>();
var t = Task.Run(()=>{
Thread.Sleep(2000);
lock((mylist as ICollection).SyncRoot)
{
for(int i=0; i<8000; i++)
{
mylist.Add(3);
Thread.Sleep(1);
}
System.Console.WriteLine($"task: list size:{mylist.Count}");
}
});
Thread.Sleep(2000);
lock((mylist as ICollection).SyncRoot)
{
for(int i=0; i<5000; i++)
{
Thread.Sleep(1);
mylist.Add(6);
}
System.Console.WriteLine($"main: list size:{mylist.Count}");
}
t.Wait();
Console.Read();
}
}
}
/*
main: list size:5000
task: list size:13000
*/
這是輸出結果,就是我們想要的。當然這個想要的,是如您業務所願。如果你是無所謂插入順序或者插入是否失敗,那麼代碼隨你寫。因此,安不安全,同步與否是需要有業務邏輯來定的。反正,如果我們想同步,那麼微軟已經爲我們準備了一個叫做SyncRoot屬性,我們不需要爲了同步某個Collection集合額外定義一個鎖對象,集合對象內部就已經替我們準備了一把叫做SyncRoot的鎖。
同樣的,其他的Collection類,也可以用這個方式實現線程安全。