天天看點

C# 異步程式設計TaskSchedulerC# 異步程式設計TaskScheduler

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();
    }
}
           

運作結果:

C# 異步程式設計TaskSchedulerC# 異步程式設計TaskScheduler

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