C#學習筆記14

来源:http://www.cnblogs.com/zwt-blog/archive/2017/01/23/6344775.html
-Advertisement-
Play Games

1.在多個線程的同步數據中,避免使用this、typeof(type)、string進行同步鎖,使用這3個容易造成死鎖。 2.使用Interlocked類:我們一般使用的互斥鎖定模式(同步數據)為Lock關鍵字(即Monitor類),這個同步屬於代價非常高的一種操作。除了使用Monitor之外,還有 ...


1.在多個線程的同步數據中,避免使用this、typeof(type)、string進行同步鎖,使用這3個容易造成死鎖。

2.使用Interlocked類:我們一般使用的互斥鎖定模式(同步數據)為Lock關鍵字(即Monitor類),這個同步屬於代價非常高的一種操作。除了使用Monitor之外,還有一個備選方案,它通常直接由處理器支持,而且面向特定的同步模式。Interlocked類中包含一些常用方法,如CompareExchange、Decrement、Increment、Exchange。這些都是針對單個的值(對象)進行同步數據處理。

3.多個線程時的事件通知:可以查看Utility.EventInThread()代碼清單。

4.同步設計的最佳實踐:

(1)避免死鎖;如兩個線程都等待對方鎖定的資源釋放,線程A鎖定sync1資源,線程B鎖定sync2資源,線程A請求鎖定sync2資源,線程B請求鎖定sync1資源,此時便出現死鎖。死鎖的發生必須滿足4個基本條件。互斥、占有並等待、不可搶先、迴圈等待條件。

(2)何時提供同步;通常針對靜態數據進行同步,並有公共方法來修改數據,方法內部應處理好同步問題。

(3)避免不必要的鎖定。

5.更多同步類型:System.Threading.Mutex類在概念上與Monitor類一致,只是其是為支持進程之間的同步。如同步對文件或其他跨進程資源的訪問,限製程序只能運行一個實例。如Utility.UseMutex()代碼清單。Mutex類派生自WaitHandle,可以自動獲取多個鎖(這是Monitor類所不支持的)。

6.WaitHandle類:多個同步類是繼承於它,如Mutex、EventWaitHandle、Semaphore,WaitHandle類的關鍵方法為WaitOne(),它有多個重載版本,這些方法會阻塞當前線程,直到WaitHandle實例收到信號或被設置(調用Set())。

7.重置事件類:重置事件與C#中委托以及事件沒有任何關係,用於多線程的控制,重置事件用於強迫代碼等候另一個線程的執行,直到獲得事件已發生的通知。重置事件類有ManualResetEvent、ManualResetEventSlim(.net4.0新增,針對前者進行優化)、AutoResetEvent(主要使用前面2個類型),它們提供的關鍵方法為Set()與Wait()。調用Wait()方法會阻塞一個線程的執行,直到一個不同的線程調用Set(),或者設定的等待時間結束,才會繼續運行。可查看Utility.UseManualResetEvent()代碼清單。

8.併發集合類:.net4.0新增了一些類是併發集合類,這些類專門設計用來包含內建的同步代碼,使它們能支持多個線程訪問而不必關心競態條件。如BlockingCollection<T>、ConcurrentBag<T>、ConcurrentDictionary<K,V>、ConcurrentQueue<T>、ConcurrentStack<T>,利用併發集合,可以實現的一個常見的模式是生產者和消費者的線程安全的訪問。

9.線程本地存儲:同步的一個替代方案是隔離,而實現隔離的一個辦法是使用線程本地存儲,利用線程本地存儲,線程就有了專屬的變數實例。線程本地存儲實現有2中方式,分別為ThreadLocal<T>和ThreadStaticAttribute類,其中ThreadLocal<T>類是.net4.0新增的。可查看LocalVarThread類的代碼清單。

10.計時器:有三種計時器分別為System.Windows.Forms.Timer、System.Timers.Timer、System.Threading.Timer,Forms.Timer用於用戶界面編程,能夠安全的訪問用戶界面上的窗體與控制項,Timers.Timer是Threading.Timer的包裝器,是對其功能的抽象(System.Threading.Timer類型輕量一些)。

功能描述

System.Timers.Timer

System.Threading.Timer

System.Window.Forms.Timer

支持在計時器實例化之後添加和刪除偵聽器

支持用戶界麵線程上的回調   

從自線程池獲取的線程進行回調

支持在Windows窗體設計器中拖放

適合在一個多線程伺服器環境中運行

支持將任意狀態從計時器初始化傳遞至回調

實現Idisposable

支持開關式回調和定期重覆回調

可穿越應用程式域的邊界訪問

支持Icomponent,可容納在一個Icontainer中

 

11.非同步編程模型(Async Program Model,APM):非同步編程是多線程的一種方式,APM的關鍵在於成對使用BeginX和EndX方法(X一般對應同步版本的方法名),而且這些方法具有完善的簽名。BeginX返回一個System.IAsyncResult對象,可通過它訪問非同步調用的狀態,以便等待完成或輪詢完成。然而EndX方法獲取這個返回的對象作為輸入參數。這樣才真正將兩個方法配成一對,讓我們可以清晰地判斷哪個BeginX方法調用和哪個EndX方法調用配對。APM的本質要求所有BeginX調用都必須有一個(而且只能有一個)EndX調用。因此,不可能發生兩個EndX調用接受同一個IAsyncResult實例的情況。我們還可以使用IAsyncResult的WaitHandle判斷非同步方法何時結束,IAsyncResult的WaitHandle是在回調執行之前進行通知完成。

  EndX方法具有4個方面的用途。

  首先,調用EndX會阻塞線程繼續執行,直到請求的工作成功完成(或者發生錯誤並引發異常)。

  其次,如果方法X要返回數據,這個數據可從EndX方法調用中訪問。

  再次,如果執行請求的工作時發生異常,可在調用EndX時重新引發這個異常,確保異常會被調用代碼發現——好像它是在一次同步調用上發生的那樣。

  最後,如果任何資源需要在調用X後清理,EndX將負責清理這些資源。

  BeginX方法有兩個額外的參數,在同步版本的方法中是沒有的,一個是回調參數,是方法結束時要調用的一個System.AsyncCallback委托,另一個是object類型的狀態參數(State)。在使用回調時,可以把EndX放在其內部執行。

12.使用TPL(任務並行庫)調用APM:雖然TPL大幅簡化了長時間運行方法的非同步調用,但通常最好是使用API提供的APM方法,而不是針對同步版本編寫TPL。這是因為API開發人員知道如何編寫最高效率的線程處理代碼,知道同步哪些數據以及要使用什麼同步類型。TPL包含FromAsync的一組重載版本,用於調用APM。

13.非同步委托調用:有一個派生的APM模式,稱為非同步委托調用,它在所有委托數據類型上使用了特殊的、由C#編譯器生成的代碼。例如,給定Func<string,int>的一個委托實例,可以在這個實例上使用以下APM方法對。

  System.IAsyncResult BeginInvoke(string arg,AsyncCallback callback,object obj)

  Int EndInvode(IasyncResult result)

  結果是可以使用C#編譯器生成的方法來同步地調用任何委托(進而調用任何方法)。遺憾的是,非同步委托調用模式使用的基礎技術是一種不再繼續開發的分散式編程技術,稱為遠程處理。雖然微軟仍然支持非同步委托調用,而且在可以預見的將來,也不會放棄對它的支持,但和其他技術相比,它的性能顯得比較一般。其他技術包括Thread、ThreadPool和TPL等。因此,在開發新項目時,開發人員應儘量選用其他技術,而不要使用非同步委托調用API。在TPL之前,非同步委托調用模式比其他替代方案容易得多,所以假如一個API沒有提供顯式的非同步調用模式,一般都會選用它。然而,在TPL問世之後,除非是為了與.Net3.5和早期框架版本相容,否則非同步委托調用越來越沒有什麼用了。

14.基於事件的非同步模式(EAP):比APM更高級的一種編程模式是基於事件的非同步模式。和APM一樣,API開發人員為長時間運行的方法實現了EAP。其中Background Worker模式,它是EAP的一個特定的實現。

15.Background Worker模式:建立Background Worker模式的過程如下。

(1)為BackgroundWorker.DoWork事件註冊長時間運行的方法。

(2)為了接受進展或狀態通知,要為BackgroundWorker.ProgressChanged掛接一個偵聽器,並將BackgroundWorker.WorkerReportsProgress設為true。

(3)為BackgroundWorker.RunWorkerCompleted事件註冊一個方法。

(4)為WorkerSupportsCancellation屬性賦值以支持取消一個操作。將true值賦給該屬性以後,對BackgroundWorker.CancelAsync的調用就會設置DoWorkEventArgs.CancellationPending標誌。

(5)在DoWork提供的方法內,檢查DoWorkEventArgs.CancellationPending屬性值,併在它為true時退出方法。

(6)一切都設置好之後,調用BackgroundWorker.RunWorkerAsync(),並提供要傳給指定DoWork()方法的一個狀態參數來開始工作。

  分解成以上小步驟以後,Background Worker模式就顯得容易理解。另外,由於它本質上是一種EAP,所以提供了對進度通知的顯式支持。後臺的工作者(worker)線程非同步執行的時候,假如發生一個未處理的異常,RunWorkerCompleted委托的RunWorkerCompletedEventArgs.Error屬性就會設置成Exception實例。因此,我們通過在RunWorkerCompleted回調內檢查Error屬性來提供異常處理機制。

16.Windows UI編程:使用System.Windows.Forms和System.Windows命名空間來進行用戶界面開發時,也必須註意線程處理問題。Microsoft Windows系列操作系統使用的是一個單線程的、基於消息處理的用戶界面。這意味著,每次只能有一個線程訪問用戶界面,與輪換線程的任何交互都應該通過Windows消息泵來封送。

17.Windows窗體:進行Windows窗體編程時,為了檢查是否允許從一個線程中發出UI調用,需要調用一個組件的InvokeRequired屬性,判斷是否需要進行封送處理。如果InvokeRequired返回true,表明需要封送,並可通過一個Invoke()調用來實現。儘管Invoke()在內部無論如何都會檢查InvokeRequired,但更有效率的做法是提前顯示地檢查這個屬性。由於封送到另一個線程可能是相當慢的一個過程,所以可以通過BeginInvoke()和EndInvoke()來進行非同步調用。Invoke()、BeginInvoke()、EndInvoke()和InvokeRequired構成了System.ComponentModel.ISynchronizeInvoke介面的成員。該介面已由System.Windows.Forms.Control實現,所有Windows窗體控制項都是從這個Control類派生。

18.Windows Presentation Foundation(WPF):為了在WPF平臺上實現相同的封送檢查,需要採取稍有不同的一種方式。WPF包含一個名為Current的靜態成員屬性,它的類型是DispatcherObject,由System.Windows.Application類提供。在調度器(dispatcher)對象上調用CheckAccess(),作用等同於在Windows窗體中的控制項上調用InvokeRequired,然後再使用Application.Current.Dispatcher.Invoke()方法封送。

19.說明:除了TPL提供的模式,還有這麼多額外的模式可供選用,這造成許多人不知道應該如何選擇。一般情況下,最好是選擇由API提供的模式(比如APM或EAP),最後選擇TPL模式。

public class Utility
{
    private static ManualResetEventSlim firstEvent, secendEvent;
    private static object _data;
    
    public static void Initialize(object newValue)
    {
        Interlocked.CompareExchange(ref _data, newValue, null);
    }

    public static void EventInThread()
    {
        //不是線程安全,在檢查委托對象與調用委托之間,存在其他線程對OnTemparatureChange進行賦值操作,可能會被設置為null。
        /*if (OnTemparatureChange != null)
        {
            //調用訂閱者
            OnTemparatureChange(this, new TemparatureEventArgs());
        }*/

        //線程安全操作,創建一個委托變數副本,檢查副本的null,再觸發副本。這樣即使OnTemparatureChange委托變數在其他線程中被null化,也不影響。
        /*TemparatureChangedHandle localChanged = OnTemparatureChange; 
        if (localChanged != null)
        {
            //調用訂閱者
            localChanged(this, new TemparatureEventArgs());
        }*/
    }

    public static void UseMutex()
    {
        bool firstApplicationInstance;
        string mutexName = Assembly.GetEntryAssembly().FullName;
        using (Mutex mutex = new Mutex(false, mutexName, out firstApplicationInstance))
        {
            if (!firstApplicationInstance)
            {
                Console.WriteLine("This Application is already running.");
            }
            else
            {
                Console.WriteLine("Enter to shutdown.");
                Console.ReadLine();
            }
        }
    }

    public static void UseManualResetEvent()
    {
        using (firstEvent = new ManualResetEventSlim())
        using (secendEvent = new ManualResetEventSlim())
        {
            Console.WriteLine("App start");
            Console.WriteLine("start task");
            Task task = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("DoWork start");
                Thread.Sleep(1000);
                firstEvent.Set();
                secendEvent.Wait();
                Console.WriteLine("DoWork end");
            });
            firstEvent.Wait();
            Console.WriteLine("Thread executing");
            secendEvent.Set();
            task.Wait();
            Console.WriteLine("Thread completed");
            Console.WriteLine("App shutdown");
        }
    }
}

public class LocalVarThread
{
    public static ThreadLocal<double> _count = new ThreadLocal<double>(() => 0.01134);

    public static double Count
    {
        set { _count.Value = value; }
        get { return _count.Value; }
    }

    public static void DoWork()
    {
        Task.Factory.StartNew(Decrement);
        for (int i = 0; i < short.MaxValue; i++)
        {
            Count++;
        }
        Console.WriteLine("DoWork Count = {0}", Count);
    }

    public static void Decrement()
    {
        Count = -Count;
        for (int i = 0; i < short.MaxValue; i++)
        {
            Count--;
        }
        Console.WriteLine("Decrement Count = {0}", Count);
    }
}
View Code

---------------------以上內容根據《C#本質論 第三版》進行整理


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...