《CLR via C#》書籍


目錄

轉自: CLR via C#--知乎,第三版
第I部分 CLR基礎
第1章 CLR的執行模型 3
1.1 將源代碼編譯成托管模塊 3
1.2 將托管模塊合並成程序集 6
1.3 加載公共語言運行時 8
1.4 執行程序集的代碼 10
1.4.1 IL和驗證 15
1.4.2 不安全的代碼 16
1.5 本地代碼生成器:NGen.exe 18
1.6 Framework類庫 20
1.7 通用類型系統 22
1.8 公共語言規范 24
1.9 與非托管代碼的互操作性 28
第2章 生成、打包、部署和管理應用程序及類型 29
2.1 .NET Framework部署目標 29
2.2 將類型生成到模塊中 31響應文件 32
2.3 元數據概述 34
2.4 將模塊合並成程序集 39
2.4.1 使用Visual Studio IDE將程序集添加到項目中 45
2.4.2 使用程序集鏈接器 46
.2.4.3 為程序集添加資源文件 48
2.5 程序集版本資源信息 49
2.6 語言文化 53
2.7 簡單應用程序部署(私有部署的程序集) 54
2.8 簡單管理控制(配置) 55
第3章 共享程序集和強命名程序集 59
3.1 兩種程序集,兩種部署 60
3.2 為程序集分配強名稱 61
3.3 全局程序集緩存 65
3.4 在生成的程序集中引用一個強命名程序集 67
3.5 強命名程序集能防范篡改 69
3.6 延遲簽名 70
3.7 私有部署強命名程序集 72
3.8 “運行時”如何解析類型引用 73
3.9 高級管理控制(配置) 76發布者策略控制 78
第II部分 設計類型
第4章 類型基礎 83
4.1 所有類型都從System.Object派生 83
4.2 類型轉換 85
4.3 命名空間和程序集 89
4.4 運行時的相互聯系 92
第5章 基元類型、引用類型和值類型 101
5.1 編程語言的基元類型 101
5.2 引用類型和值類型 108
5.3 值類型的裝箱和拆箱 113
5.3.1 使用接口更改已裝箱值類型中的字段(以及為什么不應該這樣做) 124
5.3.2 對象相等性和同一性 127
5.4 對象哈希碼 129
5.5 dynamic基元類型 131
第6章 類型和成員基礎 137
6.1 類型的各種成員 137
6.2 類型的可見性 140友元程序集 140
6.3 成員的可訪問性 142
6.4 靜態類 143
6.5 分部類、結構和接口 145
6.6 組件、多態和版本控制 146
6.6.1 CLR如何調用虛方法、屬性和事件 148
6.6.2 合理使用類型的可見性和成員的可訪問性 151
6.6.3 對類型進行版本控制時的虛方法的處理 154
第7章 常量和字段 159
7.1 常量 159
7.2 字段 160
第8章 方法 165
8.1 實例構造器和類(引用類型) 165
8.2 實例構造器和結構(值類型) 168
8.3 類型構造器 171
8.4 操作符重載方法 176
8.5 轉換操作符方法 179
8.6 擴展方法 182
8.6.1 規則和原則 184
8.6.2 用擴展方法擴展各種類型 185
8.6.3 ExtensionAttribute類 187
8.7 分部方法 188
第9章 參數 191
9.1 可選參數和命名參數 191
9.1.1 規則和原則 192
9.1.2 DefaultParameterValueAttribute和OptionalAttribute 194
9.2 隱式類型的局部變量 194
9.3 以傳引用的方式向方法傳遞參數 196
9.4 向方法傳遞可變數量的參數 201
9.5 參數和返回類型的指導原則 203
9.6 常量性 205
第10章 屬性 207
10.1 無參屬性 207
10.1.1 自動實現的屬性 210
10.1.2 合理定義屬性 211
10.1.3 對象和集合初始化器 214
10.1.4 匿名類型 215
10.1.5 System.Tuple類型 218
10.2 有參屬性 220
10.3 調用屬性訪問器方法時的性能 225
10.4 屬性訪問器的可訪問性 225
10.5 泛型屬性訪問器方法 225
第11章 事件 227
11.1 設計要公開事件的類型 228
11.1.1 第一步:定義類型來容納所有需要發送給事件通知接收者的附加信息 229
11.1.2 第二步:定義事件成員 229
11.1.3 第三步:定義負責引發事件的方法來通知事件的登記對象 231
11.1.4 第四步:定義方法將輸入轉化為期望事件 233
11.2 編譯器如何實現事件 233
11.3 設計偵聽事件的類型 235
11.4 顯式實現事件 237
第12章 泛型 241
12.1 Framework類庫中的泛型 245
12.2 Wintellect的Power Collections庫 246
12.3 泛型基礎結構 247
12.3.1 開放類型和封閉類型 247
12.3.2 泛型類型和繼承 249
12.3.3 泛型類型同一性 251
12.3.4 代碼爆炸 252
12.4 泛型接口 252
12.5 泛型委托 253
12.6 委托和接口的逆變和協變泛型類型實參 254
12.7 泛型方法 256
12.8 泛型和其他成員 258
12.9 可驗證性和約束 259
12.9.1 主要約束 261
12.9.2 次要約束 262
12.9.3 構造器約束 263
12.9.4 其他可驗證性問題 264
第Ⅲ部分 基本類型
第13章 接口 267
13.1 類和接口繼承 267
13.2 定義接口 268
13.3 繼承接口 269
13.4 關於調用接口方法的更多探討 271
13.5 隱式和顯式接口方法實現(幕后發生的事情) 272
13.6 泛型接口 274
13.7 泛型和接口約束 276
13.8 實現多個具有相同方法名和簽名的接口 277
13.9 用顯式接口方法實現來增強編譯時類型安全性 278
13.10 謹慎使用顯式接口方法實現 280
13.11 設計:基類還是接口 282
第14章 字符、字符串和文本處理 287
14.1 字符 287
14.2 System.String類型 290
14.2.1 構造字符串 290
14.2.2 字符串是不可變的 292
14.2.3 比較字符串 293
14.2.4 字符串留用 298
14.2.5 字符串池 301
14.2.6 檢查字符串中的字符和文本元素 301
14.2.7 其他字符串操作 303
14.3 高效率構造字符串 304
14.3.1 構造StringBuilder對象 304
14.3.2 StringBuilder的成員 305
14.4 獲取對象的字符串表示:ToString 307
14.4.1 指定具體的格式和語言文化 308
14.4.2 將多個對象格式成一個字符串 311
14.4.3 提供定制格式化器 313
14.5 解析字符串來獲取對象:Parse 315
14.6 編碼:字符和字節的相互轉換 317
14.6.1 字符和字節流的編碼和解碼 322
14.6.2 Base-64字符串編碼和解碼 323
14.7 安全字符串 324
第15章 枚舉類型和位標志 327
15.1 枚舉類型 327
15.2 位標志 332
15.3 向枚舉類型添加方法 335
第16章 數組 337
16.1 初始化數組元素 339
16.2 數組轉型 341
16.3 所有數組都隱式派生自System.Array 343
16.4 所有數組都隱式實現IEnumerable,Icollection和IList 344
16.5 數組的傳遞和返回 345
16.6 創建下限非零的數組 346
16.7 數組的訪問性能 347
16.8 不安全的數組訪問和固定大小的數組 351
第17章 委托 353
17.1 初識委托 353
17.2 用委托回調靜態方法 355
17.3 用委托回調實例方法 357
17.4 委托揭秘 357
17.5 用委托回調許多方法(委托鏈) 361
17.5.1 C#對委托鏈的支持 365
17.5.2 取得對委托鏈調用的更多控制 365
17.6 委托定義太多(泛型委托) 368
17.7 C#為委托提供的簡化語法 369
17.7.1 簡化語法1:不需要構造委托對象 369
17.7.2 簡化語法2:不需要定義回調方法 370
17.7.3 簡化語法3:局部變量不需要手動包裝到類中即可傳給回調方法 373
17.8 委托和反射 375
第18章 定制attribute 379
18.1 使用定制attribute 379
18.2 定義自己的attribute類 382
18.3 attribute的構造器和字段/屬性的數據類型 386
18.4 檢測定制attribute 387
18.5 兩個attribute實例的相互匹配 391
18.6 檢測定制attribute時不創建從Attribute派生的對象 393
18.7 條件attribute類 396
第19章 可空值類型 399
19.1 C#對可空值類型的支持 401
19.2 C#的空接合操作符 403
19.3 CLR對可空值類型的特殊支持 404
19.3.1 可空值類型的裝箱 404
19.3.2 可空值類型的拆箱 405
19.3.3 通過可空值類型調用GetType 405
19.3.4 通過可空值類型調用接口方法 405
第Ⅳ部分 核心機制
第20章 異常和狀態管理 409
20.1 定義“異常” 409
20.2 異常處理機制 411
20.2.1 try塊 412
20.2.2 catch塊 412
20.2.3 finally塊 414
20.3 System.Exception類 417
20.4 FCL定義的異常類 420
20.5 拋出異常 422
20.6 定義自己的異常類 423
20.7 用可靠性換取開發效率 425
20.8 指導原則和最佳實踐 433
20.8.1 善用finally塊 433
20.8.2 不要什么都捕捉 435
20.8.3 得體地從異常中恢復 436
20.8.4 發生不可恢復的異常時回滾部分完成的操作——維持狀態 436
20.8.5 隱藏實現細節來維系契約 437
20.9 未處理的異常 440
20.10 對異常進行調試 444
20.11 異常處理的性能問題 446
20.12 約束執行區域(CER) 448
20.13 代碼契約 451
第21章 自動內存管理(垃圾回收) 459
21.1 理解垃圾回收平台的基本工作原理 459
21.2 垃圾回收算法 463
21.3 垃圾回收與調試 466
21.4 使用終結操作來釋放本地資源 469
21.4.1 使用CriticalFinalizerObject類型確保終結 470
21.4.2 SafeHandle類型及其派生類型 471
21.4.3 使用SafeHandle類型與非托管代碼進行互操作 473
21.5 對托管資源使用終結操作 475
21.6 什么會導致Finalize方法被調用 477
21.7 終結操作揭秘 478
21.8 Dispose模式:強制對象清理資源 481
21.9 使用實現了Dispose模式的類型 485
21.10 C#的using語句 488
21.11 一個有趣的依賴性問題 490
21.12 手動監視和控制對象的生存期 491
21.13 對象復活 501
21.14 代 503
21.15 用於本地資源的其他垃圾回收功能 508
21.16 預測需求大量內存的操作能否成功 512
21.17 編程控制垃圾回收器 513
21.18 線程劫持 516
21.19 垃圾回收模式 517
21.20 大對象 520
21.21 監視垃圾回收 520
第22章 CLR寄宿和AppDomain 523
22.1 CLR寄宿 523
22.2 AppDomain 526
22.3 卸載AppDomain 538
22.4 監視AppDomain 540
22.5 AppDomain FirstChance異常通知 541
22.6 宿主如何使用AppDomain 541
22.6.1 可執行應用程序 542
22.6.2 Microsoft Silverlight富Internet應用程序 542
22.6.3 Microsoft ASP.NET Web窗體和XML Web服務應用程序 542
22.6.4 Microsoft SQL Server 543
22.6.5 更多的用法只局限於你自己的想象力 543
22.7 高級宿主控制 544
22.7.1 使用托管代碼管理CLR 544
22.7.2 編寫健壯的宿主應用程序 544
22.7.3 宿主如何拿回它的線程 546
第23章 程序集加載和反射 549
23.1 程序集加載 549
23.2 使用反射構建動態可擴展應用程序 554
23.3 反射的性能 555
23.3.1 發現程序集中定義的類型 556
23.3.2 類型對象的准確含義 556
23.3.3 構建Exception派生類型的一個層次結構 558
23.3.4 構造類型的實例 560
23.4 設計支持加載項的應用程序 562
23.5 使用反射發現類型的成員 564
23.5.1 發現類型成員 565
23.5.2 BindingFlags:篩選返回的成員種類 569
23.5.3 發現類型的接口 570
23.5.4 調用類型的成員 571
23.5.5 一次綁定,多次調用 575
23.5.6 使用綁定句柄來減少進程的內存耗用 581
第24章 運行時序列化 585
24.1 序列化/反序列化快速入門 586
24.2 使類型可序列化 590
24.3 控制序列化和反序列化 592
24.4 格式化器如何序列化類型實例 595
24.5 控制序列化/反序列化的數據 597
24.6 流上下文 603
24.7 將類型序列化為不同的類型以及將對象反序列化為不同的對象 604
24.8 序列化代理 606
代理選擇器鏈 609
24.9 反序列化對象時重寫程序集和/或類型 610
第Ⅴ部分 線程處理
第25章 線程基礎 615
25.1 Windows為什么要支持線程 615
25.2 線程開銷 616
25.3 停止瘋狂 620
25.4 CPU發展趨勢 622
25.5 NUMA架構的機器 623
25.6 CLR線程和Windows線程 625
25.7 使用專用線程執行異步的計算限制操作 625
25.8 使用線程的理由 627
25.9 線程調度和優先級 629
25.10 前台線程和后台線程 634
25.11 繼續學習 635
第26章 計算限制的異步操作 637
26.1 CLR線程池基礎 638
26.2 執行簡單的計算限制操作 639
26.3 執行上下文 640
26.4 協作式取消 642
26.5 任務 645
26.5.1 等待任務完成並獲取它的結果 646
26.5.2 取消任務 648
26.5.3 一個任務完成時自動啟動一個新任務 649
26.5.4 任務可以啟動子任務 651
26.5.5 任務內部揭秘 652
26.5.6 任務工廠 653
26.5.7 任務調度器 655
26.6 Parallel的靜態For,ForEach和Invoke方法 657
26.7 並行語言集成查詢(PLINQ) 660
26.8 執行定時計算限制操作 663
26.9 線程池如何管理線程 665
26.9.1 設置線程池限制 665
26.9.2 如何管理工作者線程 666
26.10 緩存線和偽共享 667
第27章 I/O限制的異步操作 671
27.1 Windows如何執行I/O操作 671
27.2 CLR的異步編程模型(APM) 675
27.3 AsyncEnumerator類 679
27.4 APM和異常 682
27.5 應用程序及其線程處理模型 683
27.6 異步實現服務器 687
27.7 APM和計算限制的操作 687
27.8 APM的注意事項 689
27.8.1 在沒有線程池的前提下使用APM 689
27.8.2 總是調用EndXxx方法,而且只調用一次 690
27.8.3 調用EndXxx方法時總是使用相同的對象 690
27.8.4 為BeginXxx和EndXxx方法使用ref,out和params實參 691
27.8.5 不能取消異步I/O限制操作 691
27.8.6 內存消耗 691
27.8.7 有的I/O操作必須同步完成 691
27.8.8 FileStream特有的問題 692
27.9 I/O請求優先級 693
27.10 將IAsyncResult APM轉換為Task 695
27.11 基於事件的異步模式 696
27.11.1 將EAP轉換為Task 698
27.11.2 APM和EAP的對比 699
27.12 編程模型的泥沼 700
第28章 基元線程同步構造 703
28.1 類庫和線程安全 705
28.2 基元用戶模式和內核模式構造 706
28.3 用戶模式構造 707
28.3.1 易失構造 708
28.3.2 互鎖構造 713
28.3.3 實現簡單的Spin Lock 717
28.3.4 Interlocked Anything模式 720
28.4 內核模式構造 722
28.4.1 Event構造 725
28.4.2 Semaphore構造 727
28.4.3 Mutex構造 728
28.4.4 在一個內核構造可用時調用一個方法 730
第29章 混合線程同步構造 733
29.1 一個簡單的混合鎖 733
29.2 自旋、線程所有權和遞歸 735
29.3 混合構造的大雜燴 737
29.3.1 ManualResetEventSlim類和SemaphoreSlim類 737
29.3.2 Monitor類和同步塊 738
29.3.3 ReaderWriterLockSlim類 743
29.3.4 OneManyLock類 745
29.3.5 CountdownEvent類 747
29.3.6 Barrier類 747
29.3.7 線程同步構造小結 748
29.4 著名的雙檢鎖技術 750
29.5 條件變量模式 754
29.6 用集合防止占有鎖太長的時間 756
29.7 並發集合類 760

筆記

轉自:每章重點和自己的思考 第三版--火赤煉 --知乎

-------------第一部分 CLR基礎------------------------
第一章 CLR基礎

        1. CLR(Common Language Runtime)是一種提供了內存管理,程序集加載,安全性,異常處理和線程同步的運行庫,個人覺得就是進程級別的虛擬機。
        2. FCL(Framework Class Library)是一組包含了數千個類型定義的DLL程序集的統稱,它包含在.NET Framework中。
        3. Microsoft創造的面向CLR的語言編譯器: C++/CLI,C#, VB, F#, Iron Python, Iron Ruby 和 IL中間語言,編譯器將所有代碼都編譯成托管模塊。
        4. 托管模塊是一個PE32或者PE32+(Portable Executable)文件,其主要包括構成:PE32(+)頭信息(windows要求的標准信息),CLR頭信息,元數據(代碼級別)和IL語言。其中元數據包含(數據類型和成員的)引用表和定義表,及可能含有清單表。
        5. 程序集是將托管模塊和資源文件合並為一個邏輯性概念,它是重用、安全性以及版本控制的最小單元,其實簡單的看就是exe和dll文件。合並的工具可以為各種編輯器或者程序集鏈接器。
        6. IL語言可以看做為一種面向對象的機器語言,它完全公開了CLR的所有功能。
        7. JIT(just-in-time即時編譯器)把IL轉換成本地CPU指令,這個發生在方法的首次調用時期。
        8. 使用NGen.exe工具可以將IL語言轉換為本地代碼,這樣可以避免運行時編譯,但是也丟失了JIT針對執行環境的高度優化。
        9. IIS等CLR宿主進程決定單個操作系統進程運行多少個AppDomain,默認情況下,每個托管EXE文件運行在一個獨立的AppDomain中,一個AppDomain占用一個獨立的地址空間。
        10. Microsoft C#編譯器允許編譯直接操作內存的代碼,但是要打開/unsafe編譯器開關。
        11. 為了統一面向CLR的語言之間的溝通,通過CLR交互,定義了CTS和CLS規范。
        12. 為了與托管代碼的交互,微軟提供了三種交互形式:托管代碼調用DLL中的非托管函數、托管代碼可以使用COM組件、非托管代碼可使用托管類型。


第二章 生成、打包、部署和管理應用程序及類型

        1. 本章針對自用程序集。.NET Framework部署將允許用戶控制安裝軟件,避免DLL hell 和 注冊表。
        2. CSC.exe文件能夠編譯腳本,響應文件(.rsp后綴)能夠設置編譯指令。
        3. ILDasm.exe 反編譯工具能夠查看PE文件。
        4. 程序集中必定有一個托管模塊包含清單文件,清單文件也是一組元數據表的集合(表中主要包含了組成程序集的那些文件的名稱,以及程序集的信息),CLR總是首先加載這個清單元數據表。


第三章 共享程序集和強命名程序集

        1. 本章重點在於如何創建可由多個應用程序訪問的程序集。
        2. 為了能夠實現程序集的更新和版本控制,采用公鑰/私鑰對程序集進行了簽名(能夠唯一標定程序集的開發商和版本),這就是強命名程序集 。
        3. .NET 會逐步要求全部的程序集都要強命名。


-------------第二部分 設計類型------------------------
第四章 類型基礎

        1. 每個實例對象都有 類型對象指針 和 同步索引塊。
        2. 類型轉換檢查

if(o is Emplyee) { Employee e = (Employee) o; }//CLR其實進行了兩次轉換檢查

Employee e = o as Emplyee
if(e != null){//}只有一次類型安全檢查

        1. 采用外部別名的方式來解決命名空間沖突問題。
        2. 類型也是一種對象,類型對象的類型對象指針指向Type類型對象。

          
第五章 基元類型、引用類型和值類型

        1. 基元類型大多數都是值類型(Int32等,但是String, Object等是引用類型);
        2. 基元類型是指編譯器直接支持的數據類型,基元類型在編譯器和FCL類型中有完全映射,如int <--->System.Int32 , float <---> System.Single,並有IL指令支持;
        3. 基元類型的溢出檢查:checked操作符和指令都是對IL溢出檢查指令的包裝, CLR基元類型才有溢出檢查。
        4. decimal是C#封裝出來的類,但不是CLR的基元類型,;
        5. Object中Equals,GetHashCode,ToString,Finalize 為虛方法,GetType,MemberwiseClone 為非虛方法;
        6. 值類型可以實現接口,值類型是隱式密封的,值類型繼承自System.ValueType,枚舉Enum繼承自ValueType。
        7. System.ValueType提供了與System.Object一樣的方法,但是重寫了Equals和GetHashCode方法,Finalize只有在垃圾回收的時候才會被調用。
        8. LayoutKind.Sequential等類型排列特性能夠保證字段在字段在內存中的排序。
        9. C#認為值類型經常用於非托管代碼操作,所以為值類型進行序列化特性,而對引用類型采用優化特性。
        10. 裝箱是指根據線程棧上的值類型生成托管堆中具有相同值的引用類型並返回其引用的過程;而拆箱是指獲取引用類型中的原始值類型的指針,此指針還是指向托管堆。
        11. 調用值類型的父類方法會造成裝箱,如Object的方法包括實方法(GetType, MemberwiseClone)和ValueType中未被重寫的虛方法(Equals, GetHashCode, ToString);
        12. 接口類型變量必須指向的是堆上的引用,所以使用值類型的接口方法,就會自動裝箱;
        13. 如果需要裝箱,請盡量顯示裝箱賦值,以避免多次隱式裝箱;
        14. c#中為了更改托管堆中已裝箱值類型的字段,只能通過將裝箱引用對象強制轉換為接口類型才行,見 p144;
        15. 由於更改值類型字段數值會帶來很嚴重的拆裝箱后果,所以建議將值類型字段設為不可變的readonly,事實上FCL核心值類型都是不可變的;
        16. 對象的相等性和同一性,Objcect.ReferenceEquals比較的同一性,Object.Equals比較的同一性(應該比較相等性),ValueType.Equals比較的相等性(采用反射遍歷實例字段);
        17. Objcect和ValueType的實例方法GetHashCode效率都不高,重寫了GetHashCode方法就不能提供唯一ID了,用RuntimeHelpers.GetHashCode靜態方法可以獲取對象的唯一ID;
        18. dynamic能夠在運行時綁定類型(也就是實現了動態語言的功能),能夠運行時調用正確的方法;在使用dynamic時,如果類型實現了DynamicMetaObjectProvider接口,那么就會調用GetMetaObject方法,實現類型綁定,如果沒有實現此接口就采用反射來執行操作;


第六章 類型和成員基礎

        1. 友元程序集,通過特性實現兩個程序集之間Internal類型的可見;
        2. 靜態類,用static標定的不可實例化的類,C#編譯器會自動將類標記為abstract和sealed;
        3. IL指令中,call以非虛方式調用方法;callvirt會核查調用對象非空,而且以多態方式調用方法;
        4. 虛方法速度比非虛方法慢,主要是由於callvirt檢查,以及虛方法不能內聯虛方法,所以盡量為簡單的方法提供重載,而不是覆寫。
        5. C#編譯器支持的partical 可以用於類,結構和接口。


第七章 常量和字段

        1. Const常量會被編譯器直接嵌入IL代碼中,所以僅更新定義常量的程序集,並不會更改常量的實際值。
        2. readonly 字段只能在構造函數中修改,static readonly 只能在靜態構造函數中修改。


第八章 方法

        1. 只有在沒有定義任何構造函數的非Static類中,C#編譯器才會隱式生成無參構造函數;
        2. 采用MemberwiseClone方法和運行時反序列化工具生成類的實例時不會調用構造函數;
        3. 類的實例字段在聲明時賦值的簡化語法在編譯過程中會把賦值過程放到所有構造函數的在開始部分執行賦值,所以可能會導致代碼膨脹;
        4. C#編譯器不會為值類型生成無參構造函數,甚至不允許顯示定義無參構造函數,
        5. 值類型允許定義有參構造函數,且任何構造函數都要初始化所有字段;
        6. 值類型不允許對實例字段在聲明時賦值(因為他不會生成默認構造函數),但是允許static字段聲明時賦值;
        7. 靜態構造函數,又叫類型構造器,類型構造器不允許帶參數,且不允許出現訪問修飾符(強制為私有private),因為靜態構造函數只能由CLR負責調用;
        8. CLR編譯方法時,如果其引用的類定義有類型構造器,且從未被調用,則會在此方法中生成代碼調用類型構造器;
        9. 類的靜態字段在聲明時賦值會導致C#在類型構造器中最開始調用賦值語句;值類型也支持靜態字段的聲明時賦值(不同於實例字段);
        10. C#中在類型構造器中創建單例對象是最佳的單例模式實現方法;
        11. 類型構造器的調用時間可以較大的影響代碼性能,顯示類型構造器比較耗費性能,而隱式生成的類型構造器可以較好的平衡性能;p195
        12. CLR要求操作符重載必須是public和static方法,且C#編譯器規定必須有參數與定義的類型相同;
        13. CLR要求類型轉換操作符必須是public和static方法,且C#編譯器規定必須有參數或者返回值與定義的類型相同;用as is操作符時,不調用類型轉換操作符;
        14. C#擴展方法要求定義在頂級靜態類中,;
        15. 擴展方法不會檢查調用方法的表達式的值是否為null,靜態方法是通過ExtensionAttribute特性來標定擴展方法的;
        16. 分部方法允許只聲明不實現,這樣編譯器就會忽略編譯,這也就導致分部方法不允許有返回值(void)或者out修飾參數符(因為方法可能不實現);
        17. 分部方法默認為private,所以不允許添加作用域關鍵字;猜測原因:如果允許外部調用分部方法,將大大降低編譯效率;


第九章 參數

        1. 允許通過命名法傳參;
        2. ref,out可以作為方法重載的標簽,但是ref 和out會視為同一個方法;
        3. ref,out不支持類型隱式轉換,為了保證類型安全;
        4. 為了盡量擴大方法的復用性:聲明方法的參數類型時,盡量指定為最弱的類型(接口弱於基類);相反,方法返回類型盡量聲明為強類型;


第十章 屬性

        1. 對象初始化簡化語法若調用無參構造器則可以省略小括號Employee e = new Employee{Name="He", Age = 45};
        2. 集合初始化簡化語法則是調用集合屬性的Add方法;
        3. 匿名類型一般之定義在方法內部,也不能泄露到方法外部;
        4. System.Tuple類是一種泛型匿名類;


第十一章 事件

        1. 事件的調用要考慮線程安全,事件的線程安全調用方法 EventHandler<T> temp = Interlocked.CompareExchange(ref NewEvent, null, null); if(temp != null)temp(this,e);P231
        2. Event就是對private delegate加上線程安全的add,remove封裝;
        3. 顯示實現事件:System.ComponentModel.EventHandlerList采用鏈表封裝了一個委托池;也可以用哈希表,加上線程安全顯示實現如p271
        4. 由於委托語法糖不需要構造委托對象,事件也可以只用聲明,而不用new實例化;


第十二章 泛型

        1. 一種-多類型鏈表(每個節點的數據類型都可以不一樣且保證類型安全的泛型鏈表)-采用繼承的實現方式p250;
        2. 泛型會引起代碼爆炸,好在CLR內置了一些優化方式;
        3. 泛型類型參數的三種形式:不變量,逆變量(in 標志),協變量(out標志);在定義泛型委托和泛型接口時,C#要求顯示標記in,out類型參數,才能支持類型參數的隱式轉換(逆變和協變);泛型類只支持不變量類型參數;p258
        4. C#編譯器支持泛型方法的類型推斷,推斷時根據的變量的類型(而不是變量的引用類型);
        5. 泛型約束沒有提供 枚舉Enum等密封類型約束,好在可以在靜態構造器中通過 typeof(T).IsEnum來判定;
        6. 泛型方法只能根據類型參數的數量進行重載,而不能根據泛型約束;
        7. C#中,屬性、索引器、事件、操作符方法、構造器和終結器(finalizer)本身不能有類型參數(但是這些方法內部可以使用類型參數變量),因為實現他們的代價太大,而作用太小;
        8. 泛型約束主要可以分為 變量類型約束(泛型類型必須是約束類及其派生類,或者實現了某個接口),變量關系約束(多個泛型類型變量之間必須有繼承關系)和構造函數約束(只有一種約束where T:new()限定了類型必須有一個無參構造器);Nullable<T>類型不滿足值類型struct約束;
        9. 普通泛型類型的變量 == null比較不會報錯,默認情況下,(值類型的變量==null) 永遠為 false;
        10. 泛型類型不能使用操作符(+、-、*、/),因為沒有實現了操作符方法類型的約束;


第十三章 接口

        1. 接口的實現方法默認為密封的,除非將實現方法顯示標記為virtual;
        2. 接口方法顯示實現時,C#編譯器要求隱式實現接口方法需要標記為public, 而顯式實現接口方法默認為private(不標記),這樣才能限制只有接口類型變量(實例變量不行)能調用顯示實現方法;
        3. 顯示實現接口方法,在派生類中沒辦法調用(實驗發現,base不能轉換為接口,貌似base只能用來調用基類的公開方法和構造函數);


-------------第三部分 基本類型------------------------
第十四章 字符、字符串和文本處理

        1. 三種方式實現Char與數值互轉(按優越性排序):強制類型轉換、System.Convert()、用Char實現了的IConvertible接口;
        2. 字符串采用Ordinal模式的意思是逐字符比較(長度肯定相同,每個字符都相同,所以可以優化比較),字符串比較時,盡量采用忽略文化模式比較StringComparison.Ordinal或者StringComparison.OrdinalIgnoreCase,因為考慮語言文化比較最耗時(不同字符,不同長度的字符串也可能相同);
        3. 微軟對執行全大寫字符串的比較進行了 優化,所以比較字符串大小盡量采用忽略文化,和忽略大小寫(自動采用全大寫比較)的模式;
        4. 變化大小寫,盡量采用ToUpperInvariant和ToLowerInvariant(對文化不敏感),而不是ToUpper或者ToLower(對文化敏感);
        5. 字符串顯式留用System.Intern(); Unity - CLR2.0默認留用;字符串留用的含義就是,相同的String引用都指向堆上同一個實例對象(以節約內存,這是通過哈希字典實現的,但是留用功能本身又比較耗性能和時間);
        6. 字符串池是編譯器對字符串文本的復用(將代碼中相同的字符串文本合並元數據中的同一個字符串),而字符串留用是指同一個String對象;
        7. ToString()和Parse要注意當前線程相關的CultureInfo,沒有仔細看,用到的時候細看;
        8. SecureString采用非托管內存緩沖區來保證加密數據的安全;


第十五章 枚舉類型和位標志

        1. 枚舉不能定義任何方法,但是可以通過擴展方法來模擬添加方法;
        2. Enum類型類似於一個結構體(一個公共實例字段<默認為int類型>,枚舉項都是本類型的Const常量);通過繼承的方法可以定義公共字段的類型

public Enum Color:byte{//等價於 public struct Color:System.Enum{
     //隱含一個字段 public byte value__;
     White, //等價於 Public const Color White = (Color)0;
}

第十六章 數組

        1. 任何數組類型都是繼承自System.Array類;
        2. 一維0基數組(數組第一位為0)也被稱為SZ數組或者向量,非0基數組開銷很大,交叉數組[][](就是0基數組)的性能優於多維數組[ , ];
        3. 數組類型轉換只能在數組元素類型之間有隱式轉換的前提下才能轉換,所以值類型數組不能參與類型轉換;
        4. 用Array.Copy()方法能夠實現任意類型的轉換(類型安全的前提下,包括拆裝箱,向下類型轉換,比如Int32[]<--->Object[], Int32[]--->Double[]);
        5. System.ConstrainedCopy()方法是保守的復制一個數組到另一個數組(元素的類型相同,或者從基類向派生類轉換),System.Buffer.BlockCopy方法是按位兼容的數據數組的復制(如 Byte[] <--->Char[]),但是沒有Copy方法的轉型功能;
        6. 所有數組隱式實現三種IEnumerable, ICollection和IList非泛型接口,所有0基一維數組還默認實現了他們的泛型接口;
        7. 約定:當方法的返回值類型為數組時,保證返回數組類型(如果為空就返回空數組,而不是null);同樣對數組類型的字段也最好有這個約定;
        8. 非0基數組可以用Array.CreatInstance()方法創建;
        9. 二維數組被視作非0基數組,安全訪問(檢查越界問題)二維數組最慢;交錯數組安全較快,但是創建過程耗時,而且產生大量的類;非安全方式訪問二維數組最快,但是限制使用;
        10. 采用stackalloc語句可以在線程棧上分配數組(只能是一維0基、純值類型元素構成的數組),這種數組性能最快p402;
        11. 采用結構中內聯數組的方式也能達到在線程棧上分配內存的目的,這種方式常用與非托管代碼互操作p403;


第十七章 委托

        1. CLR和C#都允許委托方法的協變性和逆變性;
        2. 編譯器將委托聲明實現為一個委托類,委托類繼承自MulticastDelegate類--繼承自-->Delegate類--繼承自-->Object;
        3. 委托類的構造函數為兩參構造函數(Object, IntPtr :分別為方法對象(如果是靜態方法則為null)和方法指針)分別保存在MulticastDelegate 對應的字段中;
        4. Delegate的靜態方法Target和MethodInfo可以解析委托的上述兩個字段;
        5. MulticastDelegate 還有一個_invocationList字段用來保存委托鏈;
        6. Delegate的靜態方法Combine和Remove用來實現對委托鏈的操作;
        7. 委托類自定義的Invoke方法能夠遍歷調用委托鏈的所有方法,但是方法不夠健壯(只返回最后一個方法的返回值,一個方法出現問題,后面的都會堵塞),MulticastCastDelegate的實例方法GetInvocationList方法能夠顯示調用鏈中的每個委托;
        8. 匿名函數允許操作當前方法的局部變量,但是它總是獲得最新的變量值;
        9. Delegate的靜態方法簇CreateDelegate允許根據反射得到的方法信息(運行時才能確定的方法)來創建委托,而DynamicInvoke允許調用委托對象的回調方法傳遞一組運行時確定的參數;
        10. 委托可以使用Ref, Out,Param方法,只是不能用FCL定義的Action等泛型委托;


第十八章 定制attribute

        1. 定制attribute是類的一個實例,其類型從System.Attribute派生;
        2. 利用Type.IsDefined()可以檢查類與特性的關聯;System.Attribute類的IsDefined(),GetCustomAttributes, GetCustomAttribute三個方法能夠檢測類和類型成員是否與某個Attribute關聯;(確定了Attribute類型之后,還要再確定Attribute的字段值,才能最終確定特性的設置,然后據此邏輯執行分支實現特性的效果)
        3. 為了確定Attribute實例的字段,Attribute類重寫了Equals方法(采用反射來比較字段值),還提供了一個虛方法Match;
        4. System.Reflection.CustomAttributeData類定義了GetCustomAttributes方法能夠保證在檢查定制特性時不執行特性類的構造方法或者訪問器方法(執行這些方法會帶來安全隱患,因為沒有對當前AppDomain來說是未知的);
        5. 條件Attribute,能夠避免特性代碼膨脹

Conditional("TEST") 對應代碼中定義 #define TEST

第十九章 可空值類型

        1. 在數據庫中數值可以為空,而映射到FCL中沒辦法設置為空;另外Java的Data為引用類型, 而C#對應的DataTime為值類型,兩者交互的時候也會出現類似問題;為了解決這個問題,就設計了可空值類型;
        2. public struct Nullable<T>:T//可空值類型為值類型;
        3. Nullable<Int32> x = null; 等同於 Int32? = null;
        4. CLR和C#將可空值類型盡量表現為基元類型,支持相應值類型的各種操作:轉換,轉型,操作符,拆裝箱等;
        5. 空結合操作符?? String s = SomeMethod1()?? SomeMethod2()?? "Untitled";
        6. CLR對可空值類型的裝箱:裝箱時檢測(Int32? )a == null?{直接賦值null : 取出a的值,再裝箱};拆箱亦然;


-------------第四部分 核心機制------------------------

第二十一章 自動內存管理GC

        1. 值類型、集合類型、String、Attribute、Delegate和Exception類不用執行特殊的清理操作,他們自動回收垃圾;
        2. GC判定非垃圾的第一步是查找根(線程棧、靜態字段和CPU寄存器的引用變量)的對象,第二步再查上述對象的實例字段的引用對象;
        3. 在方法中,一旦對象使用完畢(即后面的代碼不再使用某對象),此對象就會作為垃圾(即使方法沒有結束) ;但是這種情況下對調試器來說很不方便,所以微軟VS編輯器做了修改,保證在調試Debug版本,這些局部變量都會存活知道方法出棧,但是Release版本依舊會回收;【注意】此設置對Timer類造成了功能困擾,如下:

Public static void Main(){
      Timer t = new Timer(MyTimerCallback, null, 0, 2000);
Console.ReadKey();//此時t引用的Timer對象已經不可達,可以作為垃圾回收了;
t.Dispose()//如果刪掉此行代碼,那么在Release版本中,TimerCallBack方法只會執行一次(因為在第一次調用時就把t的引用對象作為垃圾回收了);
}
private static void TimerCallback(Object o){//調用垃圾回收
     Console.WriteLine("Do Sth Here");
     GC.Collect();
     

        1. Finalize方法在對象對CLR確認是垃圾時自動調用,不同對象的Finalize的調用順序不能得到保證;實現Finalize方法時需要注意:a.即使對象創建失敗,CLR也可能調用Finalize()方法而造成錯誤,解決方案見 p475;b.由於不能保證Finalize()方法執行順序,所以在Finalize()內部不能調用其他定義了Finalize方法的引用對象,因為其調用的對象可能已經提前回收了, Finalize方法中調用靜態方法也要注意靜態方法中的對象可能已經終結(Question?沒弄明白);c.Finalize()方法可能因為內存不足JIT編譯失敗或者自身原因導致不執行;
        2. 使用Finalize方法的幾種場景:向主程序發布GC通知;回收本地資源(一定要手動關閉本地資源的句柄,否則會一直留在內存中;本地資源包括 文件、網絡連接、套接字、互斥體等);
        3. 為了保證本地資源被回收,針對Finalize方法的缺點,定義CriticalFinalizerObject類保證Finalize方法一定,且最后執行;在上述類的基礎上,還定義了SafeHandle類進一步封裝本地資源的句柄指針,並提供了引用計數器功能保證多線程不沖突;CriticalHandle類不提供引用計數器,但性能更好;
        4. Finalize對GC周期的影響,定義了Finalize方法的對象在實例化對象時 會在終結列表添加一個對象的引用;GC時,掃描完所有根的引用后(終結列表的引用不算根),把終結列表中的垃圾對象引用轉移到FReachable列表,此時對象及其字段引用對象都又復活;待所有對象掃描完畢,回收普通的對象內存;執行FReachable列表的Finalize方法,並移除引用,變成普通對象;下次GC時按照普通對象回收;
        5. using語句等價於try{}finally{ IDisposable.Dispose();}的功能;
        6. GCHandle類用來監視和控制對象生存期,有的能夠影響對象周期,有的能夠固定對象地址;另外fixed語句比用GCHandle類來生成一個句柄要高效;(固定句柄多用來固定對象地址,方便與非托管代碼交互);
        7.
GC.SuppressFinalize能將對象從終結列表中移除(以不再調用Finalize方法);GC.ReRegisterForFinalize()方法用於將對象放入終結列表(在下次GC時能夠調用Finalize()方法);

        8. GC分為三代0,1,2;0代的對象最新;垃圾回收器約定越新的對象活的周期越短;GCNotification實現了垃圾回收時通知p508;GC.Collection(n)可以指定回收第0到n代的垃圾,GC.WaitForPendingFinalizers()用於在調用Finalize方法時掛起所有線程;
        9. 當引用的本地資源很大時,在需要GC清理垃圾時,需要主動提示GC實際內存消耗GC.AddMemoryPressure();以及限制本地資源數量HandleCollector類;
        10. GC.MemoryFailPoint類能夠在內存大量消耗的算法前檢查內存是否充裕;
        11. 在垃圾回收時為了保證托管代碼的執行安全,通過 線程劫持(修改線程棧讓線程掛起)或者保證線程指針執行到安全點(JIT編譯指令表中標記的偏移位置),從而安全的移動對象在內存的位置;
        12. 大對象總認為在第二代,大對象內存地址不會移動;







-------------第五部分 線程處理------------------------

第二十五章 線程基礎

        1. Windows為每個進程提供了至少一個專用線程,線程相當於邏輯CPU。p616;
        2. 線程開銷包括 內存耗用和時間開銷。主要包含上下文thread context的線程內核對象、本地存儲的線程環境塊、用戶模式棧、內核模式棧、DLL線程連接和分離通知(可以編碼關閉),以及上下文切換(也就是CPU切換運行線程,這十分耗費性能), 要盡量避免上下文切換。p617
        3. GC期間,CLR會掛起所有線程,然后檢查每個線程的根。總結:線程創建、管理、銷毀和上下文切換,以及垃圾回收的新能開銷都和線程數量正相關,所以要盡量減少線程數量p619。
        4. 最佳情況是一個CPU內核都有且只有一個線程,然而OS需要保證穩定性和響應能力,所以每個進程都會創建很多備用線程;
        5. NUMA架構的計算機 能夠緩解內存帶寬對多核CPU性能的影響,然而CLR目前還不支持對NUMA架構的控制(非托管代碼可以控制)。p624目前Win64只支持64核,Win32只支持32核。
        6. 目前CLR線程直接對應一個Windows線程,但是將來可能將邏輯線程和物理線程分離,所以編程時盡量采用FCL庫中的類型,以保證未來CLR變化時的兼容性。p625
        7. 盡量采用線程池,而不是手動創建線程(new Thread()實例),除非滿足如下任一條件(創建非普通優先級線程,創建前台線程,創建的線程會長時間運行,可能需要主動終結Abort線程)p626, 主線程調用新線程.Join()方法能夠阻塞主線程直到被調用的線程銷毀了;
        8. 線程有0~32個優先級,當存在更高優先級線程准備好運行時,系統會立即掛起當前線程(即使后者的時間片沒用完),這就是搶占式OS,它不能保證線程的執行時間。p632.
        9. 為了邏輯清晰,將優先級分為進程優先級和線程優先級,而事實上,進程優先級是系統根據啟動它的進程來分配的,而應用程序可以更改線程的相對優先級(Thread.Priority, p633)。
        10. Thread.IsBackground屬性將線程分為前台和后台,盡量使用后台線程:在進程中所有的前台線程都終結時,CLR會強制終於所有后台線程(線程池默認分配后台線程)。


第二十六章 計算限制的異步操作(就是不考慮線程同步的並行計算)

        1. 創建和銷毀線程是昂貴的操作,CLR采用啟發式線程池類來管理線程,p638;線程池的線程分為工作者worker線程和I/O線程,一般使用 “異步編程模型APM”來發出I/O請求p639。
        2. CLR默認線程池中,使用新線程的時會將上下文信息從調用線程復制到新線程,這會浪費性能,可以采用Threading.ExecutionContent類控制上下文的執行p640 。
        3. 【協作式取消】.NET支持采用Threading.CancellationTokenSource類來取消新建的線程,(在主線程中調用CancellationTokenSource.Cancel方法,能夠改變新線程中的CancellationToken.IsCancellationRequested屬性)案例見p642;還可以注冊取消CancellationTokenSource的回調方法(和執行線程);開可以建立關聯CancellationTokenSource,實現聯動取消。
        4. 【工作項】異步工作項線程在線程池中調用 ThreadPool.QueueUserWorkItem(WaitCallback callback, Object state=null),p640;支持協作式取消。
        5. 【任務Task】 為了解決QueueUserWorkItem方法發起的線程操作沒辦法知道操作在何時完成,以及沒有返回值等缺陷,Microsoft引入了任務Task的概念(Threading.Tasks的Task類及Task<TResult>泛型類)。Task支持協作式取消。
        6. Task的Wait(), WaitAll(), WaitAny(), Result等方法都會引出任務線程發出的異常(如果有的話,以集合異常AggregateException的形式封裝),如果不調用的話,異常會一直留到GC的終結期Finalize()才拋出,這時候拋出的異常可以通過TaskScheduler.UnobservedTaskException()事件登記處理方法,如果沒有登記的話,程序就會在這時中斷。
        7. Task支持CancellationTokenSource取消,支持任務鏈條,支持父子關系任務,還可以用任務工廠批量創建任務p653,最后還支持通過TaskScheduler類確定執行任務執行在 線程池的工作項線程(默認)或者同步上下文任務調度器Synchronization context task scheduler的GUI線程p655。
        8. Parallel的靜態For, ForEach和Invoke等多線程方法都是對任務Task的封裝; PLINQ並行語言集成查詢功能也是Task的封裝p660;
        9. Threading.Timer類通過線程池實現計時器(在一個線程池線程上延遲一定時間dueTime后以固定時間間隔period調用委托),Timer支持在內部更改dueTime和間隔period p663 , 如果調用的方法發生時間沖突,則會開啟更多的線程(自己實驗出來的)。
        10. System.Windows.Forms的Timer類提供的計時器與Threading的Timer不同點在於,前者只在一個新線程中計時,而調用方法這設置計時器的那個線程。
        11. 【線程池如何管理線程】盡管線程池提供了限制線程數量最大值的方法,但是盡量不要限制線程數量(可能發生死鎖);
        12. 【線程池優先調度Task】CLR線程池為每個工作者線程都分配了一個后入先出的本地隊列用來放工作者線程調度的Task對象,此外還分配了一個先入先出的全局列表用來放普通工作項(由ThreadPool.QueueUserWorkItem方法和Timer生成)和非工作者線程調度的Task對象,一個工作者線程默認先處理本地對流的Task對象,然后幫忙處理其它工作者線程本地隊列上的Task對象,最后才幫忙處理全局列表的普通工作項p667。
        13. 【CPU緩存棧導致偽共享】CPU的緩沖區會緩存相鄰的字節,可能導致不同內核數據之間需要通信,這反而會降低多線程的運行速度p668.


第二十七章 I/O限制的異步操作

        1. 在Web應用中,Windows通過可以采用同步或者異步的方式來進行IO操作,系統為每個同步IO操作存入IRP隊列(IO Request Packet)並開始進入睡眠時間,直到被IO操作結束操作系統喚醒線程並返回結果,如果客戶端的請求越來越多,就會創建大量的線程導致線程自身及上下文切換占用了大量資源;異步IO操作需要在開啟操作時聲明回調方法,然后系統將操作信息存入驅動程序的IRP隊列中,並把處理IRP結果的回調方法放入CLR的線程池隊列中,待IO結束后啟動線程池線程調用回調方法。
        2. 【APM】CLR設計的異步編程模型APM(Asynchronous Programming Model)就是上述基於線程池回調方法的總結, APM的實現就是FCL類型中大量 有成對Begin_、 End_方法的類(包括委托中的BeginInvoke方法,案例見p688); 采用APM命名管道服務器-客戶端案例p677;
        3. 為了解決APM模型需要用很多回調方法的缺點,作者利用迭代器功能對APM進行封裝實現了采用同步編程的異步操作類AsyncEnumerator【Question如何實現的】,案例見p681;
        4. APM中發生異常時,CLR會把異常封裝在IAsyncResult結果類中,並調用回調方法,需要在回調方法中處理異常;
        5. 【GUI線程執行異步IO的回調函數】在GUI應用程序(Windows窗體,WPF, Silverlight)中只有創建了窗體的線程才能刷新這個程序的數據,而控制台程序(還包括ASP.NET Web窗體和XML Web服務)允許任何線程運行;因此維保了保證APM的回調方法人就運行在GUI線程中,FCL定義了同步上下文SynchronizationContext基類,它能夠通過Post方法(主動返回,不等待)和Send方法(等待返回 ,阻塞線程池線程)保證回調函數運行在GUI線程上; p685頁對其踐行了簡單封裝並給出了案例;
        6. 任何服務器都可以用APM實現異步服務器,采用AsynEnumerator類會更加簡化編程;
        7. 【不用線程池】有時候不能用線程池或者發起APM的主線程可能需要了解異步線程是否已經計算完畢,可以通過Begin_方法的IAsyncResult類型的返回值來進行查詢,總共有三種方法:a. 在主線程中調用Begin_方法對應的End_方法<End方法只能調用一次,在回調方法中就不要再調用一次End方法了>; b.調用IAsynResult.AsyncWaitHandle.WaitOne方法;<a、b 這兩個方法都會阻塞主線程,直到異步線程操作完畢返回>;c.在主線程中輪詢IAsyncResult.IsCompleted<可以在輪詢中加入Thread.Sleep降低CPU損耗>;
        8. 只有調用了End_方法才能回收CLR為APM分配的資源,而且只能調用一次End_方法;
        9. 【取消APM線程操作】APM一般不支持取消操作,但要看IAsyncResult對象是否支持;如果有大量特別快的IO,那就用同步IO操作,因為調用APM會產生一個IAsyncResult對象產生垃圾;
        10. FileStream可以在實力化的時候指定以同步或異步方式通信,指定同步就用Read方法,指定異步就用BeginRead方法,如果混淆了會導致效率低下p692;
        11. 【IO線程優先級】目前Windows系統支持指定IO線程的優先級,但是目前FCL還沒有支持它,只能通過調用非托管代碼的方式來設置,案例p693;
        12. 【通過任務實現APM】通過任務工廠類Tasks.TaskFactory中的FromAsync方法可以實現通過任務執行I/O限制的異步操作,案例p695;
        13. 【基於事件的異步模式EAP】開發團隊認為基於IAsyncResult接口的APM對窗體開發人員太難了,就把它封裝成了基於事件的異步模式EAP,它多用在基於界面開發的模塊中(支持拖界面開發)p696;此外,任務也專門寫了一個類TaskCompletionSource類來支持EAP,p699;


第二十八章 基元線程同步構造

        1. FCL線程安全模式:對所有的靜態方法保證線程安全,對實例方法都非線程安全,但是如果實例方法是為了協調線程,也要保證這種實例方法也是線程安全(例如CancellationToken和CancellationTokenSource類的字段要用volatile標記);p705
        2. 基元線程同步構造有兩種模式,用戶模式和內核模式;p706
        3. 【***用戶模式 】是CLR直接通過特殊的CPU指令來操作線程,構造速度快,缺點是線程等待時一直在CPU上運行【活鎖,浪費內存和CPU】:
        4. 基元用戶模式的同步線程構造有兩個【易失構造(volatile)】和【互鎖構造(Interlock)】 ,他們都可以對簡單的數據類型的變量執行原子性讀寫操作和操作計時(內存柵欄);
        5. 【原子操作】有的CPU架構需要內存對齊(內存偏移數為字段長度整數倍)才支持原子操作,一般情況下CLR保證字段正確對齊,除非用FieldOffsetAttribute特性的Offset指定不對齊;
        6. 【【內存柵欄】】(Volatile和Interlocked都支持,又叫做操作計時 )指的是 運行時按照代碼順序執行讀寫操作(有時候C#編譯器,JIT編譯器和CPU都會對代碼進行優化,導致讀寫操作順便變化,以及編譯不執行代碼等,在單線程中的優化沒有問題,但是多線程中就會出現 運行時 bug,p709),它還會阻止字段進入CPU緩存(Cha26,共享字段在偽共享中會造成偽共享);
        7. 【Volatile】可以分拆為VolatileRead、VolatileWrite和MemoryBarrier三個子功能;以out ref傳引用方式傳volatile的值將會失去易失構造特性(p713);個人理解是易失操作都是針對變量做的標記,如果傳遞引用就新建了一個變量;
        8. 【靜態類Interlocked】中每一個方法都保證原子操作和內存柵欄,對不同類型支持 加減乘除Exchange/CompareExchange等功能,通過Interlocked類可通過對Int32值類型的操作 用來在不阻塞線程的情況保證一個方法只被一個線程調用等功能;見案例p714;
        9. 【自旋鎖SpinLock】通過Interlocked可以構造一個自旋鎖(用While方法不停巡視是否拿到許可),用來實現代碼區塊的同步,案例SimpleSpinLock見p717;自旋鎖浪費CPU時間,它只用來保護執行非常快的區域,且最好不用在單CPU機器,自旋鎖線程的優先級要盡量低(禁止操作系統自動動態的提升線程優先級);支持lockTaken模式;
        10. 【BlackMagic】為了減緩自旋鎖的CPU占用,FCL提供了Threading.SpinWait結構體;這個結構體采用了Thread.Sleep(0), Thread.Sleep(1), Thread.Yield()和Thread.SpinWait()四個方法暫停線程(根據方法不同,確定是否切換上下文);
        11. 【自定義Interlocked方法】Interlocked.CompareExchange()方法有Int32, Int64, Single, Double, Object和泛型引用類型多個重載版本,基於它們可以實現Multiple,Divide,Minimum,Maximum, And, Or, Xor等方法,見案例p720Maximum; 作者甚至寫了一個泛型方法p721;
        12. 【【內核模式】】是Windows操作系統內核中實現的函數,它能夠讓線程在等待時阻塞線程【死鎖,只浪費內存,好於活鎖】,缺點是鎖構造慢(代碼要在托管和本地內核模式之間切換);此外線程在用戶模式和內核模式之間切換會招致巨大的性能損失。p722
        13. 基元內核模式的線程同步構造有兩個【事件】和【信號量】,其他的內核模式都是對它們的封裝(包括互斥體);p722;
        14. 【WaitHandle】內核模式的核心是Threading.WaitHandle抽象基類,在內核對象的構造過程的所有方法都保證內存柵欄,而WaitHandler提供了對內核對象線程安全的訪問方法(Dispose, WaitOne, WaitAny, WaitAll, SignalAndWait等),操作系統會根據情況自動線程阻塞;
        15. 【事件Event】構造就是繼承WaitHandle且內核維護的Boolean變量的封裝,如果事件為false,就阻塞線程,如果事件為true,解除阻塞;根據設置變量的形式,又衍生出自動重置事件(AutoResetEvent一次只能釋放一個阻塞線程)和一個手動重置事件(ManualResetEvent 可以釋放全部的阻塞線程);
        16. 【信號量Semaphore】構造就是繼承WaitHandle且內核維護的Int32變量,信號量為0時,就阻塞線程;信號量大於0時,解除阻塞;當解除一個阻塞線程內核就自動減1,而調用 Release()方法內核變量加1;通過設置信號量初始值可以設定釋放阻塞線程的數量;p727
        17. 【互斥體Mutex】類似於一個AutoResetEvent,因為它一次只釋放一個阻塞線程,但是還有額外的功能就是線程所有權: 通過維護線程ID, 保證調用線程就是Mutex的那個線程,而且實現了遞歸鎖(線程從一個帶鎖的方法進入另一個帶鎖的方法);案例用AutoResetEvent實現了一個遞歸鎖,建議用這個遞歸鎖(因為托管代碼的實現可以減少與內核的切換,效率更高);p729;
        18. 【內核對象構造的回調方法】通過ThreadPool.RegisteredWaitHandleDemo方法能注冊一個在內核對象構造完成時候的回調方法,這樣就可以避免Wait等方法的調用,可以節約內存;p731



第二十九章 混合線程同步構造

        1. 混合線程同步構造Hybrid thread synchronization construct是綜合了用戶模式和內核模式的構造來構建的,它能夠綜合基元用戶模式構造在沒有競爭時的高效,和有競爭時基元內核模式下線程不自旋節約CPU的優點;提供了一個最簡單的混合線程同步鎖p733;
        2. 通過給等待期間增加一小段自旋時間能夠減少內核模式的切換,可能能夠進一步提高性能,另外作者給鎖增加了所有權,線程遞歸等功能,p735;
        3. FCL提供了很多混合鎖,他們的功能不一,有的推遲內核模式的構造到第一次競爭時,還能夠支持協作式取消CancellationToken(ManualResetEventSlim和SemaphoreSlim類);p737
        4. 【Monitor,同步塊】靜態類是最常用的混合線程同步構造,它的作用是維護內存中的一個同步塊(sync block)隊列(每個同步塊包含一個內核對象、線程ID、遞歸計數 和一個等待線程計數);在構造對象時,同步塊索引指向-1,調用Monitor.Enter(Object)方法時,CLR將對象的同步塊索引指向一個新的同步塊,並更新同步塊的數據;當再次調用Monitor.Enter方法時更新遞歸計數或者等待線程計數;當調用Monitor.Exit方法時,會檢查等待線程,重置計數或者設置對象的同步塊索引為-1;p738
        5. 現有的Monitor非常容易導致線程堵塞,而且難以調試,示例p740;為了避免這個問題,強烈建議專門設置一個私有對象的同步塊索引作為同步鎖,一般就用Object對象;示例p740
        6. 【lock是Monitor,try ,finally的語法糖】C#語言提供了lock關鍵字類簡化Monitor同步鎖, 它用Finally來保證Monitor.Exit是一件非常不好的做法,因為這樣會隱藏線程異常,讓程序帶病運行,p742; 如果線程在進入try塊,而在調用Monitor.Enter方法錢退出,那么可以通過lockTaken變量(Boolean類型)來確定在Fanilly塊中要不要調用Monitor.Exit方法;
        7. 【讀寫鎖】ReaderWriterLockSlim類是一個讀寫鎖構造,讀取線程可以同步執行,但會阻塞寫入線程,寫入線程會堵塞其它寫入線程和讀取線程;ReaderWriterLockSlim類可以支持遞歸(代價很高,需要一個互斥自旋鎖),支持將reader級別提升為write級別(代價很高,不建議使用),此外已經廢棄了性能很差的ReaderWriterLock構造。p743
        8. 【自定義讀寫鎖】作者基於Interlocked類操作位bit 實現了OneManyLock類的讀寫鎖,性能高於FCL提供的讀寫鎖,p745;
        9. CountdownEvent類 類似於Semaphore;p747
        10. Barrier類 能夠讓多個線程按照階段運行,等待其他線程都完成了一個階段之后,再一起進入下一個階段;p748
        11. 【多線程單例】在單例模式中,雙檢鎖是指兩次if判定是否為空;有兩點指的關注,由於C#有內存柵欄,可以保證CPU緩存的s_Value變量一定是真實的(Java的鎖沒有內存柵欄);new實例化對象時一定要先復制給臨時變量,再用基元同步構造賦值給引用;

Singleton temp = new Singleton();
Interlocked.Exchange(ref s_value, temp);
//s_value = new Singleton()//編譯器可能先在內存中分配一塊地址給s_value,然后再給內存調用構造器,這期間另外一個線程可能 就會使用這個不完整的內存對象,這個bug一般不可重復。

        1. 【最好的單例】其實就是直接用默認類型構造函數,private static Singleton s_value = new Singleton();此外還有利用Interlocked.CompareExchange技術將實例化放到普通靜態屬性中的單例模式;p752
        2. 泛型類Lazy<T>將線程安全單例的三種方式進行了封裝(適合GUI程序而不考慮線程安全的模式,雙檢鎖技術,Interlocked.CompareExchange技術);同樣的還有Threading.LazyInitializer類;
        3. 當希望一個線程在條件為true的時候執行代碼,如果一直自旋判定條件非常耗費性能,可以通過給條件加內核基元鎖實現,實現了一個線程安全且檢查隊列長度的隊列Queue, p755;
        4. 【集合改造讀寫鎖】在服務器的讀寫鎖中,當一個寫鎖鎖定資源,如果新來的讀取請求很多,它們只會新建線程並堵塞;當寫入線程釋放鎖時,大量的讀取線程會導致嚴重的上下文切換;為了解決這個問題,采用Berrier類、Task以及隊列 來分批次控制讀取線程的創建;作者由此發明了ReaderWriterGate和AsyncGate類;p759;
        5. 【並發集合類】FCL自帶四個線程安全集合類: ConcurrentQueue <T>(FIFO),ConcurrentStack<T>(LIFO), ConcurrentBag<T>(無序),ConcurrentDictionary<TK,TV>(無序);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM