談談ThreadStatic屬性用法

風靈使發表於2019-01-08

如果一個型別包含非靜態欄位(例項欄位),則對於該欄位,該型別的每個例項均有其自身的獨立儲存位置;在一個例項中設定欄位並不影響其他例項中該欄位的值。而相反,對於靜態欄位,無論有多少例項,該欄位只位於一個儲存位置(或者,更具體地說,在每個 AppDomain 中,只位於一個儲存位置)。然而,如果將 System.ThreadStaticAttribute 應用於靜態欄位,則該欄位將變為執行緒靜態欄位,即,對於該欄位,每個執行緒(而非例項)將保留其自身的儲存位置。在一個執行緒上設定執行緒靜態的值將不會影響其在其他執行緒上的值。

可能經常做多執行緒、執行緒池的童鞋早就知道這種問題,原諒我一直對執行緒研究不深。

這個東西好像出現有一段時間了,不過最近我才用到,做的API的服務,用來儲存當前請求的上下文內容,原來用過Thread.SetData,不過原來的使用者量沒這麼大,沒發現問題。

查了一些關於ThreadStatic的說明,有一些人說好,也有人說坑的,的確有坑,下面看個例子:

class Program
{
    [ThreadStatic]
    private static int? NowI;
    //private static string resultString = "";
 
    static void Main(string[] args)
    {
        //resultString = "";
        for (var i = 0; i < 100; i++)
        {
            int i1 = i;
            var t = new Task(() =>
            {
                if(NowI == null) NowI = i1;
                //var nowI = Thread.GetData(Thread.GetNamedDataSlot("NowI"));
                //if (nowI == null)
                //{
                //    nowI = i1;
                //    Thread.SetData(Thread.GetNamedDataSlot("NowI"), i1);
                //}
 
                Console.WriteLine(string.Format("第{0}次迴圈, i值:{1},執行緒ID:{2}\r\n", i1, NowI, Thread.CurrentThread.ManagedThreadId));
            });
            t.Start();
        }
        //Console.WriteLine(resultString.ToString());
        Console.Read();
    }
}

NowI儲存在ThreadStatic中,迴圈100次,開100個執行緒,執行結果(結果沒排序,不過已經可以看出問題了):

第1次迴圈, i值:1,執行緒ID:12

第2次迴圈, i值:1,執行緒ID:12

第3次迴圈, i值:3,執行緒ID:15

第0次迴圈, i值:0,執行緒ID:6

第5次迴圈, i值:5,執行緒ID:17

第4次迴圈, i值:4,執行緒ID:14

第6次迴圈, i值:6,執行緒ID:11

第12次迴圈, i值:6,執行緒ID:11

第13次迴圈, i值:6,執行緒ID:11

第14次迴圈, i值:6,執行緒ID:11

第15次迴圈, i值:6,執行緒ID:11

第16次迴圈, i值:6,執行緒ID:11

第17次迴圈, i值:6,執行緒ID:11

第18次迴圈, i值:6,執行緒ID:11

第19次迴圈, i值:6,執行緒ID:11

第20次迴圈, i值:6,執行緒ID:11

第21次迴圈, i值:6,執行緒ID:11

第22次迴圈, i值:6,執行緒ID:11

第23次迴圈, i值:6,執行緒ID:11

第24次迴圈, i值:6,執行緒ID:11

......

可以看出, 執行緒ID一樣的,儲存的NowI變數的值是一樣的,再猜,可能是GC沒有回收垃圾,然後在new Task最後加上GC.Collect,然而並沒有什卵用。

問題原因是這樣的:當執行緒重用時,垃圾回收並沒有回收上次的空間,其中變數值依然存在,如果不重新賦值,必然出現延用上次值的現象。

所以,解決辦法就是線上程結束前把ThreadStatic變數清空。

再看結果:

第0次迴圈, i值:0,執行緒ID:10

第3次迴圈, i值:3,執行緒ID:12

第4次迴圈, i值:4,執行緒ID:10

第1次迴圈, i值:1,執行緒ID:11

第5次迴圈, i值:5,執行緒ID:12

第6次迴圈, i值:6,執行緒ID:10

第9次迴圈, i值:9,執行緒ID:10

第8次迴圈, i值:8,執行緒ID:12

第13次迴圈, i值:13,執行緒ID:12

......

這次就正常了。

不過,有沒有辦法可以統一對執行緒結束後進行處理呢,把所有這種變更重置一下,目前還沒找到,希望大神們弄過的給個意見。

相關文章