[C#.NET][Thread] 善用 SpinWait 處理 執行緒空轉 以利提昇效能
當我們在處理一個執行緒時,若需要同步等待時,以往可能會常用 Thread.Sleep,但 Thread.Sleep 會消耗 CPU 的時間配置,所以我們可以使用 Thread.SpinWait 方法 、SpinWait 結構
在 .NET4.0 以前,可以使用 Thread.SpinWait 方法
下圖出自http://msdn.microsoft.com/zh-tw/library/system.threading.thread.spinwait.aspx
在 .NET4.0 以後,可以使用 SpinWait 結構
下圖出自http://msdn.microsoft.com/zh-tw/library/ee722114.aspx
下圖出自http://msdn.microsoft.com/zh-tw/library/system.threading.spinwait.aspx
看來,MSDN 則是建議使用 SpinWait 結構
來看個例子:
以往我常用 Stopwatch 來搭配 Thread.Sleep 來達到空轉等待的目的
public void Start(Action action) { if (this.IsRunning) { return; } this.IsRunning = true; Task.Factory.StartNew(() => { Stopwatch watch = new Stopwatch(); while (this.IsRunning) { watch.Restart(); action.Invoke(); while (watch.ElapsedMilliseconds < this.Interval) { Thread.Sleep(1); } } }); }
把 this.Interval = 1 觀察工作管理員結果,CPU 大約用掉了 2% (這在不同的機器會有不同的結果)。
若是把 Thread.Sleep(1) 拿掉,CPU 負荷將近半載
現在則把 Thread.Sleep拿掉,改用 SpinWait.SpinUntil 來運行空轉等待。
第一個參數是離開空轉的條件,第二個參數是離開空轉的時間,只要任一參數滿足,則離開空轉。
public void Start(Action action) { if (this.IsRunning) { return; } this.IsRunning = true; Task.Factory.StartNew(() => { while (this.IsRunning) { action.Invoke(); SpinWait.SpinUntil(() => !this.IsRunning, this.Interval); } }); }
我們同樣用 this.Interval = 1 來觀察空轉的效果,結果是 0%,很明顯的這樣的寫法的確是勝於上一個方法。
結論:
PS.基本上 UI 更新的越快CPU飆的越高,若沒有空轉 UI 沒辦法更新。
所以我們可以把 Thread.Sleep(1) 可以換成 SpinWait.SpinUntil(() => false, 1)
完整程式碼,它沒有防呆,請不要將 TextBox 設成 0 或空。
public class Polling { private int _interval = 1000; public int Interval { get { return _interval; } set { _interval = value; } } public bool IsRunning { get; internal set; } public void Start(Action action) { if (this.IsRunning) { return; } this.IsRunning = true; Task.Factory.StartNew(() => { while (this.IsRunning) { action.Invoke(); SpinWait.SpinUntil(() => !this.IsRunning, this.Interval); } }); } public void Stop() { if (!this.IsRunning) { return; } this.IsRunning = false; } }
建立Winform專案,建立以下控制項
用戶端,調用方式,這裡是使用 SynchronizationContext 更新 UI
public partial class Form1 : Form { public Form1() { InitializeComponent(); m_SynchronizationContext = SynchronizationContext.Current; } private SynchronizationContext m_SynchronizationContext; private Polling _polling = new Polling(); private int _counter = 0; private void button1_Click(object sender, EventArgs e) { this._polling.Interval = int.Parse(this.textBox1.Text); this._polling.Start(() => { this._counter++; m_SynchronizationContext.Post(a => { this.label1.Text = this._counter.ToString(); }, null); }); } private void button2_Click(object sender, EventArgs e) { this._polling.Stop(); } }
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET