C# 異步程式設計TaskScheduler
1.Task
Task任務,其本身不會執行任何代碼,需要使用線程來執行Task的代碼,預設情況下Task的運作線上程池中的線程中。Task類并沒有提供Thread. Abort這樣強制結束的函數,因為Task代碼不是由自己本身執行,而是由線程Thread執行。如果Task已經線上程Thread中執行,調用了Task. Abort這樣的函數強制結束,此時執行它的線程将不知道下一步要執行的代碼,是以Task沒有Abort這樣的成員函數。Task本身不應該去殺死執行它線程,線程不屬于某一個Task,對于Task來說線程是公共資源,如果殺死線程隻會違背Task的初衷,Task本身也就是為了減少建立線程重複利用線程而設計的。
2. TaskScheduler
當一個Task需要運作時,首先需要添加到TaskScheduler類的一個隊列中排隊,TaskScheduler會從隊列中取Task,放到線程中執行,預設情況下的TaskScheduler會将Task放到線程池中的線程上運作。
Task.Start和Task.ContinueWith都包含了可以傳入TaskScheduler參數的版本,利用這個參數可以控制Task在哪個線程上運作。
以下我們建立了一個自己的TaskScheduler類,在TaskScheduler類中建立一條單獨的線程用來執行Task。
class MyTaskScheduler : TaskScheduler
{
public static new TaskScheduler Current { get; } = new MyTaskScheduler();
public static new TaskScheduler Default { get; } = Current;
private readonly BlockingCollection<Task> m_queue = new BlockingCollection<Task>();
MyTaskScheduler()
{
Thread thread = new Thread(Run);
thread.IsBackground = true;//設為為背景線程,當主線程結束時線程自動結束
thread.Start();
}
private void Run()
{
Console.WriteLine($"MyTaskScheduler, ThreadID: {Thread.CurrentThread.ManagedThreadId}");
Task t;
while (m_queue.TryTake(out t, Timeout.Infinite))
{
TryExecuteTask(t);//在目前線程執行Task
}
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return m_queue;
}
protected override void QueueTask(Task task)
{
m_queue.Add(task);//t.Start(MyTaskScheduler.Current)時,将Task加入到隊列中
}
//當執行該函數時,程式正在嘗試以同步的方式執行Task代碼
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return false;
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"Main, ThreadID: {Thread.CurrentThread.ManagedThreadId}");
for (int i = 0; i < 10; i++)
{
var t = new Task(() =>
{
Console.WriteLine($"Task, ThreadID: {Thread.CurrentThread.ManagedThreadId}");
});
t.Start(MyTaskScheduler.Current);
}
Console.ReadKey();
}
}
運作結果:
3. TaskScheduler與await關鍵字
以下是一個使用了await的函數:
static async Task<int> fun()
{
await AsyncFun();
Run();
}
這函數執行的代碼和以下代碼類似:
Task t = AsyncFun();
var currentContext = SynchronizationContext.Current;
if (null == currentContext)
{
t.ContinueWith((te) => { Run(); }, TaskScheduler.Current);
}
else
{
t.ContinueWith((te) => { Run(); }, TaskScheduler.FromCurrentSynchronizationContext());
}
函數會先擷取SynchronizationContext.Current對象,這個對象預設情況下,在控制台程式中為null,在GUI程式中不為null。當SynchronizationContext.Current==null時會直接将後續代碼當作一個Task在TaskScheduler.Current上運作,如果不等于null,則會将代碼在如下TaskScheduler.FromCurrentSynchronizationContext()上。
在GUI程式中SynchronizationContext.Current對象的Post方法和Send方法可以發送一段代碼給主線程執行,對于函數TaskScheduler.FromCurrentSynchronizationContext傳回值實際上其内部會調用Post方法,将代碼發送到主線程執行,這樣主線程調用的函數使用了await,函數await後面的代碼依然會在主線程執行,可以防止其他線程調用主線程UI控件而崩潰。
擴充閱讀
在控制台程式中使用SynchronizationContext對象
- Await, SynchronizationContext, and Console Apps: Part 1
- Await, SynchronizationContext, and Console Apps: Part 2
- Await, SynchronizationContext, and Console Apps: Part 3
.Net異步程式設計
- Parallel Programming with .NET