好久沒有更新了,今天來一篇,算是《同步與異步》系列的開篇吧,加油,堅持下去(PS:越來越懶了)。
一、Thread
利用Thread 可以直接建立和控制線程,在我的認知裡它是最古老的技術了。因為out了、是以不再寫例子了。
二、ThreadPool
由于線程的建立和銷毀需要耗費大量的資源,為了提過性能、引入了線程池、即ThreadPool,ThreadPool 可隐式完成線程的建立和配置設定管理工作。
以下是來自MSDN的幾句備注:
線程池根據需要提供新的工作線程或 I/O 完成線程,直到其達到每個類别的最小值。 當達到最小值時,線程池可以在該類别中建立更多線程或等待某些任務完成。 從 .NET Framework 4 開始,線程池會建立和銷毀工作線程以優化吞吐量,吞吐量定義為機關時間内完成的任務數。 線程過少時可能無法更好地利用可用資源,但線程過多時又可能會加劇資源的争用情況。
直接應用 ThreadPool時、有兩種應用場景:
1、将需要異步執行的方法、排入隊列,當有可用線程時執行被排入隊列的方法
// 該應用場景下 ThreadPool 提供如下兩種形式的重載方法
// public static bool QueueUserWorkItem(WaitCallback callBack);
// public static bool QueueUserWorkItem(WaitCallback callBack, object state);
// WaitCallback 為 delegate, 一個object類型的入參,沒有傳回值。
// public delegate void WaitCallback(object state);
base.SetTip(nameof(ThreadPool.QueueUserWorkItem));
ThreadPool.QueueUserWorkItem((state) =>
{
this.SetTip("等待一秒");
Thread.Sleep(1000);
this.SetTip("任務執行完畢");
});
2、注冊等待信号對象(WaitHandle)、并在其收到信号或逾時時觸發回調函數
// 該應用場景下 ThreadPool 提供了四種形式的重載方法, 下面的重載形式在我看來是最具有直覺意義的
// public static RegisteredWaitHandle RegisterWaitForSingleObject(
// WaitHandle waitObject, WaitOrTimerCallback callBack, object state, long millisecondsTimeOutInterval, bool executeOnlyOnce);
// 其中 WaitHandle 為需要等待信号的類型 的 抽象基類,在後續随筆中會做詳細介紹,在此不再多言。
// 其中 WaitOrTimerCallback 為 回調函數委托
// public delegate void WaitOrTimerCallback(object state, bool timedOut);
// state 參數為 回調函數的傳入參數
// millisecondsTimeOutInterval 參數為 計時器逾時周期, -1 為永不逾時、即一直等待。
// 對于此參數、第一次接觸到的人可能有疑問、怎麼還有周期?
// 因為 信号可以重複接到多次、是以當每次接到信号後、或者逾時後計時器都會重新計時, 是以有了周期的含義。
// executeOnlyOnce, True 表示隻執行一次, False 會一直等到該信号對象被取消注冊,否則 隻要接到信号或者逾時就會觸發回調函數。
base.SetTip(nameof(ThreadPool.RegisterWaitForSingleObject));
ThreadPool.RegisterWaitForSingleObject(this.waitObject, (state, timeout) =>
{
this.SetTip("++++++等待對象收到信号++++++");
}, null, -1, false);
ThreadPool.QueueUserWorkItem((state) =>
{
this.SetTip("等待一秒");
Thread.Sleep(1000);
this.SetTip("等待對象發出信号");
this.waitObject.Set();
this.SetTip("等待5秒");
Thread.Sleep(5000);
this.SetTip("等待對象發出信号");
this.waitObject.Set();
});
三、Delegate.BeginInvoke
Delegate.BeginInvoke 間接的調用了線程池、從線程池中擷取一個可用線程、執行目前委托所指向的函數指針所代表的方法。
[Tag("Delegate.BeginInvoke")]
private void Demo3()
{
this.txtTip.SetTip("UI, Id:" + Thread.CurrentThread.ManagedThreadId);
Action action = new Action(this.DelegateTest);
action.BeginInvoke(null, null);
action.BeginInvoke(null, null);
}
private void DelegateTest()
{
int id = Thread.CurrentThread.ManagedThreadId;
this.Dispatcher.Invoke(() =>
{
this.txtTip.SetTip("BeginInvoke, Id:" + id);
});
}
通過 Thread.CurrentThread.ManagedThreadId 、我們也可以間接的證明 BeginInvoke 确實時在不同的線程中執行的。
當然,與之對應的是一系列的 Begin... End... 方法對, 它們都是 IAsyncResult 系列的異步程式設計模型中的一份子。
在 IAsyncResult 系列的異步程式設計模型中,傳遞參數 和 擷取傳回值的Demo 見下:
[Tag("Delegate.BeginInvoke帶有參數和傳回值")]
private void Demo4()
{
Func<string, string> func = new Func<string, string>(this.DelegateTest2);
this.txtTip.SetTip(" 輸入參數 123 ");
func.BeginInvoke(" 123 ", new AsyncCallback((System.IAsyncResult result) =>
{
Func<string, string> tmp = result.AsyncState as Func<string, string>;
if (tmp != null)
{
string returnResult = tmp.EndInvoke(result);
this.Dispatcher.Invoke(() =>
{
this.txtTip.SetTip(" 函數回調 結果 : " + returnResult);
});
}
}), func);
}
private string DelegateTest2(string args)
{
return args + " : Return args";
}
四、Task
Task是在 .NET 4.0 中新增的,是基于任務的異步模型。它是對線程池的再次封裝、使得可以更加友善的建立多種任務組合,如 順序性的延續任務、父子任務。也可以友善的粗粒度地控制任務優先級。
1)Task.NET 4.0 中提倡的方式
Task 對外公開了構造函數、但是微軟并不建議直接使用Task構造函數去執行個體化對象,而是 使用 Task.Factory.StartNew();
MSDN 中的備注如下:
For performance reasons, TaskFactory's StartNew method should be the preferred mechanism for creating and scheduling computational tasks,
but for scenarios where creation and scheduling must be separated, the constructors may be used,
and the task's Start method may then be used to schedule the task for execution at a later time.
Task 類還提供了初始化任務但不計劃執行任務的構造函數。 出于性能方面的考慮,TaskFactory 的 StartNew 方法應該是建立和計劃計算任務的首選機制,
但是對于建立和計劃必須分開的情況,可以使用構造函數,然後可以使用任務的 Start 方法計劃任務在稍後執行。
Task.Factory.StartNew(() =>
{
base.SetTip("Task.Factory.StartNew(一個參數)");
}).ContinueWith((t) =>
{
base.SetTip(t.Id.ToString());
base.SetTip(t.CreationOptions.ToString());
}).ContinueWith((t) =>
{
base.SetTip(t.Id.ToString());
base.SetTip(t.CreationOptions.ToString());
});
Task.Factory.StartNew 提供了高達16個的重載函數。
其中 Task.Factory.StartNew<TTask> 是建立帶有傳回值的異步任務。
以最複雜的重載為例、逐一介紹其參數
public Task<TResult> StartNew<TResult>(Func<object, TResult> function, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler);
function : 回調函數、我想沒有必要做解釋吧。
state : 回調函數的傳入參數
CancellationToken : 用以取消Task (後續随筆會做詳細介紹)
TaskCreationOptions : 指定可控制任務的建立和執行的可選行為的标志(後續随筆會做詳細介紹)
TaskScheduler : 一個執行個體 TaskScheduler 類表示一個任務計劃程式. 該值一般都是使用 TaskScheduler.Default
也就是說:
Task.Factory.StartNew(()=> { });
和
Task.Factory.StartNew(()=> { }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
兩種方式效果是一緻的。
如果你想更精細化的控制任務、可以使用其他重載方法、傳遞不同參數以達到預想的目的。
2)Task初始化任務但并不計劃執行
前文說過 Task 提供了構造函數、它提供了初始化任務,但并不去計劃執行的方式。
讓我們再看一下 Task 得構造函數吧,還是以最複雜的為例:
public Task(Func<object, TResult> function, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions);
其參數和 Task.Factory.StartNew 相比、 少了TaskScheduler。在性能方面MSDN提示後者會更好。
base.SetTip("初始化任務");
Task task = new Task(() =>
{
base.SetTip("被計劃的任務開始執行");
base.SetTip("任務休眠5秒");
Thread.Sleep(5000);
base.SetTip("任務執行完畢");
});
// 為了保證能實時更新UI、看到代碼執行效果、故而将代碼異步執行
Task.Factory.StartNew(() =>
{
base.SetTip("休眠兩秒");
Thread.Sleep(2000);
base.SetTip("将任務列入執行計劃");
task.Start();
base.SetTip("等待Task執行完畢");
task.Wait();// Wait方法 會等待Task執行完畢
base.SetTip("Task執行完畢");
});
另外再強調一點:Task.Start(), 隻是将任務列入執行計劃,至于任務什麼時候去執行則取決于線程池中什麼時候有可用線程。
Task.Factory.StartNew 也是一樣的。
好了,Task類的介紹、到此為止,後續随筆再做詳細介紹:這個強大的Task類,還有很多值得我們去探索的東西。
本随筆到此、暫告一段落。
附,Demo : https://files.cnblogs.com/files/08shiyan/ParallelDemo.zip
參見更多:随筆導讀:同步與異步
(未完待續...)
傳回導讀目錄,閱讀更多随筆
分割線,以下為部落格簽名:
軟體臭蟲情未了
- 編碼一分鐘
- 測試十年功
随筆如有錯誤或不恰當之處、為希望不誤導他人,望大俠們給予批評指正。