從一個簡單的異步操作開始
System.Console.WriteLine("主線程:開啟一個異步操作...");
ThreadPool.QueueUserWorkItem((o) => {
for (int i = 0; i < 100; i++)
{
System.Console.WriteLine("異步操作:{0}",i);
Thread.Sleep(100);
}
});
System.Console.WriteLine("主線程:其他任務...");
Thread.Sleep(1000);//模擬其他任務
System.Console.WriteLine("主線程任務完成");
執行上下文
每個線程都關聯了一個執行上下文的數據結構。執行上下文包括:安全設置、宿主設置以及邏輯上下文數據。
線程執行代碼時,有的操作會受到線程的執行上下文的影響。理想情況下,每一個線程(初始線程)使用另一個線程(輔助線程)執行任務時,前者執行上下文應該流向(復制到)輔助線程。確保輔助線程執行的任何操作使用的是相同的安全設置和宿主設置,初始線程的邏輯調用上下文可以在輔助線程中使用。
默認情況下,CLR自動將初始線程上下文復制到輔助線程中,這樣會對性能造成一定的影響,尤其是這些數據沒有用的時候,為了避免這種情況,可以使用ExecutionContext
類阻止執行上下文流動。
CallContext.LogicalSetData("Name", "Jeffrey");
ThreadPool.QueueUserWorkItem((o) =>
{
System.Console.WriteLine("Name:{0}", CallContext.LogicalGetData("Name"));
});
//阻止當前線程上下文流動
ExecutionContext.SuppressFlow();
ThreadPool.QueueUserWorkItem((o) =>
{
System.Console.WriteLine("Name:{0}", CallContext.LogicalGetData("Name"));
});
協作式取消
在進行耗時操作的時候,如果我們想取消,且不然代碼繼續執行,是否可以實現?答案當然是可以的。為了取消一個操作,首先必須創建一個System.Threading.CancellationTokenSource
對象。
static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
ThreadPool.QueueUserWorkItem(o=>Count(cts.Token,1000));
System.Console.WriteLine("Press <Enter> to Cancel opreation");
System.Console.ReadLine();
cts.Cancel();//如果Count方法已經返回,Cancel沒有任何效果
System.Console.ReadKey();
}
private static void Count(CancellationToken token, int v)
{
for (int i = 0; i < v; i++)
{
if (token.IsCancellationRequested)
{
System.Console.WriteLine("Count is Canceled");
break;
}
System.Console.WriteLine(i);
Thread.Sleep(200);
}
System.Console.WriteLine("Count is done");
}
任務
雖然通過ThreadPool.QueueUserWorkItem()
方法可以執行異步操作,但是它依然有一些局限,比如無法知道異步操作何時完成,獲取返回值。為了解決這些問題,微軟提供了Task
類。
等待任務完成并獲取結果
Task<int> task = new Task<int>(() =>
{
int sum = 0, t = 1000000;
for (; t > 0; t--)
{
sum += t;
}
return sum;
});
//可以指定啟動的時機
task.Start();
//可以顯示等待任務完成
task.Wait();
//獲取結果(Result內部會執行Wait方法)
System.Console.WriteLine(task.Result);
取消任務
var cts = new CancellationTokenSource();
Task<int> task = new Task<int>(() => Count(cts.Token, 100), cts.Token);
//可以指定啟動的時機
task.Start();
//在某個時刻,調用取消CancellationTokenSource以取消任務
Thread.Sleep(500);
cts.Cancel();
private static int Count(CancellationToken token, int v)
{
int r = 0;
for (int i = 0; i < v; i++)
{
if (token.IsCancellationRequested)
{
r = i;
System.Console.WriteLine("Count is Canceled");
break;
}
System.Console.WriteLine(i);
Thread.Sleep(200);
}
System.Console.WriteLine("Count is done");
return r;
}
一個任務完成時自動啟動一個新任務
Task<int> task = new Task<int>(() =>
{
Thread.Sleep(1000);
return 0;
});
task.Start();
task.ContinueWith(t => System.Console.WriteLine(t.Result));
任務工廠
當需要創建一組Task對象來共享相同的狀態,為了避免將相同的參數一個個傳遞給Task的構造器,可以通過創建一個任務工廠封裝通用的狀態。
TaskCreationOptions
:指定用于控制任務的創建和執行的可選行為的標志。
TaskContinuationOptions
:為通過使用 Task.ContinueWith
或 Task<TResult>.ContinueWith
方法創建的任務指定行為。此枚舉有一個 FlagsAttribute
特性,通過該特性可使其成員值按位組合。
TaskScheduler
:表示一個處理將任務排隊到線程中的低級工作的對象。
static void Main(string[] args)
{
Task parent = new Task(() => {
var cts = new CancellationTokenSource();
var tf = new TaskFactory<int>(cts.Token, TaskCreationOptions.AttachedToParent, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
var childTasks = new[] {
tf.StartNew(()=>Sum(cts.Token,10000)),
tf.StartNew(()=>Sum(cts.Token,1500)),
tf.StartNew(()=>Sum(cts.Token,int.MaxValue))
};
for (int i = 0; i < childTasks.Length; i++)
{
childTasks[i].ContinueWith(t => cts.Cancel(), TaskContinuationOptions.NotOnFaulted);
}
tf.ContinueWhenAll(
childTasks,
c => c.Where(w => !w.IsFaulted && !w.IsCanceled).Max(m => m.Result),
CancellationToken.None).
ContinueWith(c => Console.WriteLine("Max:{0}", c.Result), TaskContinuationOptions.ExecuteSynchronously);
});
parent.ContinueWith(c => {
StringBuilder sb = new StringBuilder("Exceptions :" + Environment.NewLine);
foreach (var item in c.Exception.Flatten().InnerExceptions)
{
sb.AppendLine(" "+item.GetType().ToString());
}
},TaskContinuationOptions.OnlyOnFaulted);
parent.Start();
Console.ReadKey();
}
private static int Sum(CancellationToken token, int v)
{
int returnValue = 0;
for (int i = 0; i < v; i++)
{
//在取消標志引用的CancellationTokenSource上,如果調用Cancel,ThrowIfCancellationRequested()方法拋出異常
token.ThrowIfCancellationRequested();
//if (token.IsCancellationRequested)
//{
// break;
//}
returnValue += i;
}
return returnValue;
}
Parallel的靜態For,ForEach和Invoke方法
Parallel
靜態類的內部是通過Task實現的。Parallel的方法可以并行執行任務。他封裝了任務在某些情況下的編碼簡便方法。
//線程池的線程并行處理
Parallel.For(0, 100, i => DoWork(i));
//ForEach的效率要比For差一些
Parallel.ForEach(collection, item => DoWork(item));
Parallel.Invoke(() => DoWork1(), DoWork2());
Parallel
本身的創建也是會有一定的開銷,如果只是為了完成幾項非耗時的方法,不需要通過Parallel
來完成。
Parallel
的循環方法中,如果有其中一個方法拋出了異常,Parallel方法最后會拋出一個AggregateException
。
Parallel
方法內執行的函數要避免修改共享數據,Parallel的并發性容易造成數據狀態異常,當然也可以加同步線程鎖來控制,但是這樣Parallel
的并發執行任務就沒有效果了。
計時器
System.Threading.Timer
:如果要在一個線程池上執行定時的(周期性發生的)后臺任務時,最好選用它。
System.Windows.Forms.Timer(System.Windows.Threading.DispatcherTimer)
:調用win32的SetTimer函數,所有工作只由一個線程完成,所以它不會并發完成任務。
System.Timers.Timer
:Jeffrey Richter說這是微軟頭昏腦脹的時候搞出來的一個類,除非你在設計平面上想用一個計時器。
一般計時器都會遇到這個問題:比如設置的計時器是每秒調用一次方法,但是方法執行比較耗時,超過了一秒,這個時候就會遇到同時再執行同一個方法,為了避免這種問題
static Timer _timer;
static void Main(string[] args)
{
//Timeout.Infinite=-1;表示時鐘只執行一次
using (_timer = new Timer(ProcessCallback, 5, 0, Timeout.Infinite))
{
Console.WriteLine("Do SomeThing...");
Thread.Sleep(10000);
}
Console.ReadKey();
}
private static void ProcessCallback(object state)
{
Console.WriteLine("Process:{0}", state);
Thread.Sleep(1000);//模擬耗時操作
//繼續調用
_timer.Change(2000, Timeout.Infinite);
}
上面的這段代碼,到了10秒周_timer.Change()
會引發對象已經釋放掉的異常(使用using
),大家可以通過_timer.Dispose()
方法釋放_timer
的資源
文章列表