>>回傳《C# 并發編程》
- 1. 調度到執行緒池
- 2. 任務調度器
- 2.1. Default 調度器
- 2.2. 捕獲當前同步背景關系 調度器
- 2.3. ConcurrentExclusiveSchedulerPair 調度器
- 3. 調度并行代碼
- 4. 用調度器實作資料流的同步
1. 調度到執行緒池
Task task = Task.Run(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(2));
});
Task.Run 也能正常地回傳結果,能使用異步 Lambda 運算式,下面代碼中 Task.Run 回傳的 task 會在 2 秒后完成,并回傳結果 13:
Task<int> task = Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
return 13;
});
Task.Run 回傳一個 Task (或 Task<T>)物件,該物件可以被異步或回應式代碼正常使用,
注意: 但不要在 ASP.NET 中使用
Task.Run,除非你有絕對的把握,在 ASP.NET 中, 處理請求的代碼本來就是在 ASP.NET 執行緒池執行緒中運行的,強行把它放到另一個執行緒池執行緒通常會適得其反,
但UI程式,使用Task.Run可以執行耗時操作,有效的防止頁面卡住問題,
在進行動態并行開發時, 一定要用 Task.Factory.StartNew 來代替 Task.Run
- 因為根據默認配置,
Task.Run回傳的Task物件適合被異步呼叫(即被異步代碼或回應式代碼使用), Task.Run也不支持動態并行代碼中普遍使用的高級概念,例如 父/子任務,
2. 任務調度器
需要讓多個代碼段按照指定的方式運行,例如
- 讓所有代碼段在 UI 執行緒中運行
- 只允許特定數量的代碼段同時運行,
2.1. Default 調度器
TaskScheduler.Default,它的作用是讓任務在執行緒池中排隊, Task.Run、并行、資料流的代碼用的都是 TaskScheduler.Default,
2.2. 捕獲當前同步背景關系 調度器
可以捕獲一個特定的背景關系,用 TaskScheduler.FromCurrentSynchronizationContext 調度任務,讓它回到該背景關系:
TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext();
這條陳述句創建了一個捕獲當前 同步背景關系 的 TaskScheduler 物件,并將代碼調度到這個背景關系中
SynchronizationContext類表示一個通用的調度背景關系,- 大多數 UI 框架有一個表示 UI 執行緒的 同步背景關系
- ASP.NET 有一個表示 HTTP 請求的 同步背景關系
建議:
在 UI 執行緒上執行代碼時,永遠不要使用針對特定平臺的型別,\
- WPF、IOS、Android 都有
Dispatcher類 - Windows 應用商店平臺使用
CoreDispatcher - WinForms 有
ISynchronizeInvoke介面(即Control.Invoke)
不要在新寫的代碼中使用這些型別,就當它們不存在吧,使用這些型別會使代碼無謂地系結在某個特定平臺上,
同步背景關系 是通用的、基于上述型別的抽象類,
2.3. ConcurrentExclusiveSchedulerPair 調度器
它實際上是兩個互相關聯的調度器, 只要 ExclusiveScheduler 上沒有運行任務, ConcurrentScheduler 就可以讓多個任務同時執行,只有當 ConcurrentScheduler 沒有執行任務時, ExclusiveScheduler 才可以執行任務,并且每次只允許運行一個任務:
public static void ConcurrentExclusiveSchedulerPairRun()
{
var schedulerPair = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, maxConcurrencyLevel: 2);
//由于并行被限流,所以ConcurrentScheduler 會兩個兩個輸出,然后執行完這兩個開啟的8個串行任務
TaskScheduler concurrent = schedulerPair.ConcurrentScheduler;
TaskScheduler exclusive = schedulerPair.ExclusiveScheduler;
//Default 由于沒有限制,所以第一層會先輸出,全部隨機
// TaskScheduler concurrent = TaskScheduler.Default;
// TaskScheduler exclusive =TaskScheduler.Default;
var list = new List<List<int>>();
for (int i = 0; i < 4; i++)
{
var actionList = new List<int>();
list.Add(actionList);
for (int j = 0; j < 4; j++)
{
actionList.Add(i * 10 + j);
}
}
var tasks = list.Select(u => Task.Factory.StartNew(state =>
{
System.Console.WriteLine($"ConcurrentScheduler");
((List<int>)state).Select(i => Task.Factory.StartNew(state2 => System.Console.WriteLine($"ExclusiveScheduler:{state2}"), i, CancellationToken.None, TaskCreationOptions.None, exclusive)).ToArray();
}, u, CancellationToken.None, TaskCreationOptions.None, concurrent));
Task.WaitAll(tasks.ToArray());
}
輸出:
ConcurrentScheduler
ConcurrentScheduler
ExclusiveScheduler:0
ExclusiveScheduler:1
ExclusiveScheduler:2
ExclusiveScheduler:3
ExclusiveScheduler:10
ExclusiveScheduler:11
ExclusiveScheduler:12
ExclusiveScheduler:13
ConcurrentScheduler
ConcurrentScheduler
ExclusiveScheduler:20
ExclusiveScheduler:21
ExclusiveScheduler:22
ExclusiveScheduler:23
ExclusiveScheduler:30
ExclusiveScheduler:31
ExclusiveScheduler:32
ExclusiveScheduler:33
ConcurrentExclusiveSchedulerPair 的常見用法是
- 用
ExclusiveScheduler來確保每次只運行一個任務, ExclusiveScheduler執行的代碼會在執行緒池中運行,但是使用了同一個ExclusiveScheduler物件的其他代碼不能同時運行,
ConcurrentExclusiveSchedulerPair 的另一個用法是作為限流調度器,
- 創建的
ConcurrentExclusiveSchedulerPair物件可以限制自身的并發數量, - 這時通常不使用 ExclusiveScheduler
var schedulerPair = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default,maxConcurrencyLevel: 8);
TaskScheduler scheduler = schedulerPair.ConcurrentScheduler;
3. 調度并行代碼
public static void RotateMatricesRun()
{
List<List<Action<float>>> actionLists = new List<List<Action<float>>>();
for (int i = 0; i < 15; i++)
{
var actionList = new List<Action<float>>();
actionLists.Add(actionList);
for (int j = 0; j < 15; j++)
{
actionList.Add(new Action<float>(degrees =>
{
Thread.Sleep(200);
System.Console.WriteLine("degrees:" + degrees + " " + DateTime.Now.ToString("HHmmss.fff"));
}));
}
}
RotateMatrices(actionLists, 10);
//雖然兩個并行嵌套但是由于調度器的設定,導致任務是8個8個執行的,結果是8個后200ms再8個
}
static void RotateMatrices(IEnumerable<IEnumerable<Action<float>>> collections, float degrees)
{
var schedulerPair = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, maxConcurrencyLevel: 8);
TaskScheduler scheduler = schedulerPair.ConcurrentScheduler;
ParallelOptions options = new ParallelOptions
{
TaskScheduler = scheduler
};
Parallel.ForEach(collections, options,
matrices =>
{
Parallel.ForEach(matrices,
options,
matrix => matrix.Invoke(degrees)
);
System.Console.WriteLine($"============");
});
}
輸出:
degrees:10 190424.120
... 118個 ...
degrees:10 190426.963
============
============
============
============
============
============
============
============
degrees:10 190427.167
... 6個 ...
degrees:10 190427.167
... 5個 ...
degrees:10 190428.589
... 6個 ...
degrees:10 190428.589
degrees:10 190428.791
degrees:10 190428.791
degrees:10 190428.791
degrees:10 190428.791
degrees:10 190428.791
degrees:10 190428.791
============
degrees:10 190428.791
degrees:10 190428.791
degrees:10 190428.994
... 6個 ...
degrees:10 190428.994
============
degrees:10 190429.194
... 6個 ...
degrees:10 190429.194
============
degrees:10 190429.395
degrees:10 190429.395
degrees:10 190429.395
degrees:10 190429.395
degrees:10 190429.395
============
degrees:10 190429.395
degrees:10 190429.395
degrees:10 190429.395
degrees:10 190429.598
degrees:10 190429.598
degrees:10 190429.598
degrees:10 190429.598
============
degrees:10 190429.598
degrees:10 190429.598
degrees:10 190429.598
degrees:10 190429.598
============
degrees:10 190429.800
============
4. 用調度器實作資料流的同步
Stopwatch sw = Stopwatch.StartNew();
// 模擬 UI同步背景關系
AsyncContext.Run(() =>
{
var options = new ExecutionDataflowBlockOptions
{
//使用次調度器,則代碼會放到創建執行緒的同步背景關系上執行(若是當前同步背景關系是UI Context 或 此例的AsyncContext)
//運行和注釋下行運行觀察Creator和Executor執行緒Id的變化
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(),
};
var multiplyBlock = new TransformBlock<int, int>(item => item * 2);
System.Console.WriteLine($"Creator ThreadId: {Thread.CurrentThread.ManagedThreadId}.");
var displayBlock = new ActionBlock<int>(result =>
{
// ListBox.Items.Add(result)
System.Console.WriteLine($"Executor ThreadId: {Thread.CurrentThread.ManagedThreadId} res:{result}.");
}, options);
multiplyBlock.LinkTo(displayBlock);
for (int i = 0; i < 5; i++)
{
multiplyBlock.Post(i);
System.Console.WriteLine($"Post {i}");
}
multiplyBlock.Completion.Wait(2000);
});
System.Console.WriteLine($"Cost {sw.ElapsedMilliseconds}ms.");
輸出:
Creator ThreadId: 1.
Post 0
Post 1
Post 2
Post 3
Post 4
Executor ThreadId: 1 res:0.
Executor ThreadId: 1 res:2.
Executor ThreadId: 1 res:4.
Executor ThreadId: 1 res:6.
Executor ThreadId: 1 res:8.
Cost 2062ms.
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/75337.html
標籤:C#
上一篇:執行緒同步
下一篇:實用技巧
