[C#.NET][Thread] 善用 SpinWait 處理 執行緒空轉 以利提昇效能

[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

image

 

 

在 .NET4.0 以後,可以使用 SpinWait 結構

下圖出自http://msdn.microsoft.com/zh-tw/library/ee722114.aspx

image

 

 

下圖出自http://msdn.microsoft.com/zh-tw/library/system.threading.spinwait.aspx

image

 

看來,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% (這在不同的機器會有不同的結果)。

SNAGHTML137db49f_thumb

 

若是把 Thread.Sleep(1) 拿掉,CPU 負荷將近半載

SNAGHTML137f38fd_thumb


現在則把 Thread.Sleep拿掉,改用 SpinWait.SpinUntil 來運行空轉等待。

image

 

第一個參數是離開空轉的條件,第二個參數是離開空轉的時間,只要任一參數滿足,則離開空轉。


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%,很明顯的這樣的寫法的確是勝於上一個方法。

SNAGHTML1385dd20_thumb

 

結論:

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專案,建立以下控制項

image

用戶端,調用方式,這裡是使用 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();
    }
}

若有謬誤,煩請告知,新手發帖請多包涵

2010~2017 C# 第四季