C# 异步编程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 m_queue =   new BlockingCollection();

    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 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# 异步编程TaskScheduler_第1张图片

3. TaskScheduler与await关键字

以下是一个使用了await的函数:

        static async Task 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时会直接将后续代码当作一个TaskTaskScheduler.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

 

你可能感兴趣的:(C#)