C# Task

Task作为C#异步的核心,类中的每个方法有必要学习一番,而部分重点方法更要尝试分析一下源码。

首先,Task位于System.Threading.Tasks命名空间下。

官方对其定义:Represents an asynchronous operation.

先看一下Task的类注释,这里讲了很多重点。

第一条注释:

Task instances may be created in a variety of ways.The most common approach is by using the Task type's Factory property to retrieve a System.Threading.Tasks.TaskFactory instance that can be used to create tasks for serveral purposes

也就是说,我们可以使用Task.Factory.StartNew这个方法的很多重载方式去创建一个适合应用场景的Task,下面是应用实例:

static void Main(string[] args)
{
     Console.WriteLine("Main1 Thread" + Thread.CurrentThread.ManagedThreadId);
     Task.Factory.StartNew(() => 
     {
         Console.WriteLine("Main2 Thread" + Thread.CurrentThread.ManagedThreadId);
     });
     Console.ReadLine();
}

//结果:
Main1 Thread1
Main2 Thread3

第二条注释:

The Task class also provides constructors that initialize the Task but that do not schedule it for execution. For performance reasons, TaskFactory's StartNew method should be the preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation and scheduling must be separated, the constructors may be used, and the task's Start() method may then be used to schedule the task for execution at a later time.

意思就是

如果你想建立一个Task并且立即执行它,使用Task.Factory.StartNew(),这样做性能更好

如果你想建立一个Task,想在合适的时候执行它,那么使用Task构造器做初始化:

static void Main(string[] args)
{
    //初始化这个task,但不执行
    Task task = new Task(() => 
    {
       //doSomething
    });
    //做第一件事
    //做第二件事
    //做第N件事
    //之后再执行这个task
    task.Start();
    Task.Factory.StartNew(() => { });
}

这样更加灵活。

第三条注释:

All members of Task, except for Dispose(),are thread-safe and may be used from multiple threads concurrently.

除了Dispose()方法,Task的其他所有成员都是线程安全的!

第四条注释:

For operations that return values, the System.Threading.Tasks.Task{TResult} class should be used.

如果你想使用有返回值的Task,可以使用Task<Tresult>:

Task<string> task = new Task<string>(() =>
{
    //doSomething
    return "可以返回泛型类型的值";
});
Console.WriteLine("task的返回值是:" + task.Result);

输出:
可以返回泛型类型的值

最后一条注释,表述了一些成员变量的作用:

For developers implementing custom debuggers, several internal and private members of Task may be useful (these may change from release to release).
①The Int32 m_taskId field serves as the backing store for the Id property, however accessing this field directly from a debugger may be more efficient than accessing the same value through the property's getter method (the s_taskIdCounter Int32 counter is used to retrieve the next available ID for a Task). 
②the Int32 m_stateFlags field stores information about the current lifecycle stage of the Task,information also accessible through the Status property. 
③The m_action System.Object field stores a reference to the Task's delegate, and the m_stateObject System.Object field stores the async state passed to the Task by the developer. Finally, for debuggers that parse stack frames, the InternalWait method serves a potential marker for when a Task is entering a wait operation.

①直接使用m_taskId比使用ID的getter方法更好,因为其实可以从源码看到:

public int Id 
{ 
    get 
    { 
        if (m_taskId == 0) 
        { 
            int newId = NewId(); 
            Interlocked.CompareExchange(ref m_taskId, newId, 0); 
        } 
        return m_taskId; 
    } 
}

Id的值是从m_taskId中取的。

②m_stateFlags和Status都记录了当前Task的生命周期阶段,而Status的值是从m_stateFlags中取的:

public TaskStatus Status
{
    get
    {
        TaskStatus rval;

        int sf = m_stateFlags;
 
        if ((sf & TASK_STATE_FAULTED) != 0) rval = TaskStatus.Faulted;
        else if ((sf & TASK_STATE_CANCELED) != 0) rval = TaskStatus.Canceled;
        else if ((sf & TASK_STATE_RAN_TO_COMPLETION) != 0) rval = TaskStatus.RanToCompletion;
        else if ((sf & TASK_STATE_WAITING_ON_CHILDREN) != 0) rval = TaskStatus.WaitingForChildrenToComplete;
        else if ((sf & TASK_STATE_DELEGATE_INVOKED) != 0) rval = TaskStatus.Running;
        else if ((sf & TASK_STATE_STARTED) != 0) rval = TaskStatus.WaitingToRun;
        else if ((sf & TASK_STATE_WAITINGFORACTIVATION) != 0) rval = TaskStatus.WaitingForActivation;
        else rval = TaskStatus.Created;
 
        return rval;
     }
 }

③m_action存储Task的委托的引用,m_stateObject存储异步状态。然而当我想用vs开始一些测试时,发现根本调用不了这些成员,这里我就想到了我之前从《CLR via C#》当中看到的这么一段话:

高级语言(包括C#)通常只公开了CLR全部功能的一个子集。然而IL汇编语言允许开发人员访问CLR的全部功能。

所以,如果我想探寻这些变量的秘密,或许我嘚从IL入手(这是后面再回顾笔记的留言 :这里根本不是因为上面这些鬼扯,明明就是这两个变量是internal的访问权限而已...)。

④InternalWait()方法会在Wait()方法中被调用,作为判断task是否完成的一个标记。

public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
{
	//......
    // Wait, and then return if we're still not done.
    if (!InternalWait(millisecondsTimeout, cancellationToken))
        return false;
    //......
}

说完了Task的重点注释之后,我们来看一下Task实现了哪些接口:

IAsyncResult:

encapsulate the results of an async operation 封装异步的操作结果

成员:

//判断异步操作是否完成
bool IsCompleted 
//该对象主要是有一些等待异步操作完成的动作
WaitHandle AsyncWaitHandle
//可设置的异步状态,比较灵活
Object AsyncState
//判断异步操作是否同步完成
bool CompeltedSynchronously

IDisposable:

Interface for assisting with deterministic finalization.用于释放资源的接口.

成员:

//做资源释放的动作
void Dispose()

好了,基类看完了,再来看一下Task类的重点,属性与方法。

首先,在学习时,应注重两点:

①作用是什么

②如何使用(实践操作)

那么我们需要先知道去初始化Task对象,这里Task提供了一共8个构造器:

public Task(Action action);
public Task(Action action, CancellationToken cancellationToken);
public Task(Action action, TaskCreationOptions creationOptions);
public Task(Action<object> action, object state);
public Task(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions);
public Task(Action<object> action, object state, CancellationToken cancellationToken);
public Task(Action<object> action, object state, TaskCreationOptions creationOptions);
public Task(Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions);

不同的构造器实用于不同的场景,先来看第一个:

public Task(Action action);

①以一个Action委托作为入参的Task构造器:

static void Main(string[] args)
{
    Action action = () => 
    {
        Console.WriteLine("The delegate method is executed");
    };
    Task task = new Task(action);
    task.Start();
    Console.ReadLine();
}

out:

第一个比较简单,也比较常用,通常我们会将任务放到Action中然后给Task执行。

②Action<obejct>,object作为入参的Task构造器:

public Task(Action<object> action, object state);
static void Main(string[] args)
{
    Action<object> action = (o) => 
    {
        Console.WriteLine("The delegate method is executed,state is :" + o);
    };
    object obj = "success";
    Task task = new Task(action,obj);
    task.Start();
    Console.ReadLine();
}

out:

obj会被作为action的入参。

③Action,CancellationToken作为入参的Task构造器:

public Task(Action action, CancellationToken cancellationToken); 
static void Main(string[] args)
{
    var tokenSource = new CancellationTokenSource();
    CancellationToken ct = tokenSource.Token;

    Action action = () =>
    {

        Console.WriteLine("I have been performed");
        ct.ThrowIfCancellationRequested();
        Console.WriteLine("Maybe I won't be executed");
    };
    Task task = new Task(action);
    task.Start();
    tokenSource.Cancel();
    Console.ReadLine();
}

out:

主程序中,执行了Cancel方法,所以在Task执行任务时调用ThrowIfCancellationRequested方法检测到了Cancel操作,遂中止task,抛出异常,但因异常在Task线程,所以不会对主线程造成影响。

这种方式创建的Task可以起到相应控制的作用。

④以Action,TaskCreationOptions作为Task入参的构造器:

TaskCreationOptions是一个枚举类,一共有7个枚举值:

参考:C#指定TaskCreationOptions来创建Task - 辰宸的备忘录

public enum TaskCreationOptions
{
    None = 0,
    PreferFairness = 1,
    LongRunning = 2,
    AttachedToParent = 4,
    DenyChildAttach = 8,
    HideScheduler = 16,
    RunContinuationsAsynchronously = 64
}	

TaskCreationOptions.None:一个一个来试一下吧:

static void Main(string[] args)
{
   Action action = () =>
   {
       Console.WriteLine("The delegate is executing");
   };
   TaskCreationOptions creationOptions = TaskCreationOptions.None;
   Task task = new Task(action, creationOptions);
   task.Start();
   Console.ReadLine();
}

TaskCreationOptions.PreferFairness:枚举值的描述是指定默认行为,所以可能这个枚举值设定和不设定应该对Task没有影响吧。

提示TaskScheduler以一种公平的方式去执行Task,越早计划的Task会越早执行,相反,越晚计划的Task将会越晚执行。用例待补充。

TaskCreationOptions.LongRunning:

默认的TaskScheduler采用的是.NET线程池ThreadPool,它主要面向的是细粒度的小任务,其执行时间通常在毫秒级。线程池中的线程数与处理器的内核数有关,如果线程池中没有空闲的线程,那么后续的Task将会被阻塞。因此,如果事先知道一个Task的执行需要较长的时间,就需要使用TaskCreationOptions.LongRunning枚举指明。使用TaskCreationOptions.LongRunning创建的任务将会脱离线程池启动一个单独的线程来执行。

先来看看本机有的cpu是几核的:

现在设定一个任务,里面进行while(true){}循环让线程永远被占用,4核就跑5个task:

static void Main(string[] args)
{
    Action action = () =>
    {
        Console.WriteLine("The delegate is executing:" + Thread.CurrentThread.ManagedThreadId);
        while (true){ }
    };
    TaskCreationOptions creationOptions = TaskCreationOptions.None;
    //启动4个task
    for(int i = 0; i < 4; i++)
    {
		Task.Run(action);
    }
	//启动第5个task
    Task task = new Task(action, creationOptions);
    task.Start();
    Console.ReadLine();
}

结果:

可以看到只有4个task被启动,第5个task被阻塞了。

当使用第5个Task使用TaskCreationOptions.LongRunning之后

static void Main(string[] args)
{
    Action action = () =>
    {
        Console.WriteLine("The delegate is executing:" + Thread.CurrentThread.ManagedThreadId);
        while (true){ }
    };
    TaskCreationOptions creationOptions = TaskCreationOptions.LongRunning;
    //启动4个task
    for(int i = 0; i < 4; i++)
    {
		Task.Run(action);
    }
	//启动第5个task
    Task task = new Task(action, creationOptions);
    task.Start();
    Console.ReadLine();
}

结果:

第5个task正常运行。

TaskCreationOptions.AttachedToParent:

正常情况下,如果在一个Task内部再建一个Task,两个Task位于2个线程,互相不会影响

static void Main(string[] args)
{
    TaskCreationOptions creationOptions = TaskCreationOptions.None;
    var parent = Task.Factory.StartNew(() =>
    {
         Console.WriteLine("parent is executing");
         var child = Task.Factory.StartNew(() =>
         {
            Console.WriteLine("child is executing,Father wating me");
            Thread.Sleep(10000);
            Console.WriteLine("child task completing");
          }, creationOptions);
    });
    parent.Wait();
    Console.WriteLine("Outer has completed");
    Console.ReadLine();
}

结果:

可以看到,外层Task执行完毕,而后内层Task才执行完毕。

现在来让外层Task等一下内层Task:

static void Main(string[] args)
{
    TaskCreationOptions creationOptions = TaskCreationOptions.AttachedToParent;
    var parent = Task.Factory.StartNew(() =>
    {
         Console.WriteLine("parent is executing");
         var child = Task.Factory.StartNew(() =>
         {
            Console.WriteLine("child is executing,Father wating me");
            Thread.Sleep(10000);
            Console.WriteLine("child task completing");
          }, creationOptions);
    });
    parent.Wait();
    Console.WriteLine("Outer has completed");
    Console.ReadLine();
}

结果:

可以看到,外层Task会被wait住,等待内层Task执行完成,然后整个Task才会执行完成。

另外,通过对内层Task设定TaskCreationOptions.AttachedToParent,内层Task抛出异常外层Task也可捕获处理。

TaskCreationOptions.DenyChildAttach:

如果内层设定了TaskCreationOptions.AttachedToParent让外层去等待自己执行完成,但外层不想等待,就可以给外层设定TaskCreationOptions.DenyChildAttach:

static void Main(string[] args)
{
    var parent = Task.Factory.StartNew(() =>
    {
         Console.WriteLine("parent is executing");
         var child = Task.Factory.StartNew(() =>
         {
            Console.WriteLine("child is executing,Father wating me");
            Thread.Sleep(10000);
            Console.WriteLine("child task completing");
          }, TaskCreationOptions.AttachedToParent);
    },TaskCreationOptions.DenyChildAttach);
    parent.Wait();
    Console.WriteLine("Outer has completed");
    Console.ReadLine();
}

结果:

这样外层Task又会忽略内层Task的执行了。

TaskCreationOptions.HideScheduler:

//创建这个Task使用TaskScheduler.Current
Task task = Task.Factory.StartNew(action);
//创建这个Task使用TaskScheduler.Default
Task task1 = Task.Factory.StartNew(action, TaskCreationOptions.HideScheduler);

但如何证实具体使用的哪个TaskScheduler以及两个TaskScheduler的区别还不清楚。

TaskCreationOptions.RunContinuationsAsynchronously 待补充

剩余的其他构造方法便是Action,Action<object>,CancellationToken,TaskCreationOptions几个参数的不同组合了,到底运用哪个Task就要看实际的场景。

现在再来看一下Task的所有属性:

属性作用
static int CurrentId 返回当前正在执行的Task的ID
static TaskFactory Factory 创建Task的工厂类
static Task CompletedTask 获取已成功完成的Task。
TaskCreationOptions CreationOptions获取创建Task时指定的TaskCreationOptions 
bool IsCompleted判断当前Task是否完成执行
bool IsCanceled 判断当前Task是否由于被取消而完成
TaskStatus Status获取当前Task的状态
AggregateException Exception获取当前Task是否是因为过早结束而抛出的异常,如果当前Task成功的执行完成,那么会返回Null.
int Id获取当前Task实例的ID
object AsyncState获取当前Task的异步状态
bool IsFaulted判断当前Task是否由于未处理异常而完成

CurrentId:

static void Main(string[] args)
{ 
    Task task = new Task(() =>
    {
         Console.WriteLine("CurrentId is " + Task.CurrentId);
     });
         Task task1 = new Task(() =>
         {
         Console.WriteLine("CurrentId is " + Task.CurrentId);
     });
     task.Start();
     task1.Start();
     Console.ReadLine();
}

结果:

Id和CurrenctId看起来很像,并且也是同一个值,只是CurrentId只能在task的运行环境中获取。而Id就比较灵活了,因为是实例相关,只要可以使用该task实例对象的地方都可以获取到实例task的Id.

static void Main(string[] args)
{ 
    Task task = new Task(() =>
    {
        Console.WriteLine("CurrentId is " + Task.CurrentId);
    });
    task.Start();
    Console.WriteLine("Id is " + task.Id);
    Console.ReadLine();
}

结果:

Factory:

Factory顾名思义,创建Task的工厂类:

public class Program
{
    static void Main(string[] args)
    {
        //1.最常见的方式
        Task<string> task = Task.Factory.StartNew(() => { return "I am the return value of task"; });
        //2.创建的task的委托方法运行条件是等到传入的异步操作完成
        Task.Factory.FromAsync(task, (asyncResult) => 
        {
            Task<string> task1 = (Task<string>)asyncResult;
            Console.WriteLine("第二种方式:" + task1.Result + "---------------------");
        });
        //3.当传入的一组task都完成之后执行委托方法
        Task<string> task2 = Task.Factory.StartNew(() => { return "I am the return value of task2"; });
        Task<string> task3 = Task.Factory.StartNew(() => { return "I am the return value of task3"; });
        Task<string>[] tasks = { task2, task3 };
        Task.Factory.ContinueWhenAll(tasks, (o) =>
         {
             for (int i = 0; i < o.Length; i++)
             {
                 Console.WriteLine("第三种方式:" + o[i].Result);
             }
              Console.WriteLine("-------------------------");
         });
        //4.传入的一组task中的任何一个完成,完成的那个task将继续执行委托方法
        Task.Factory.ContinueWhenAny(tasks, (o) =>
         {
             Console.WriteLine("第四种方式:" + o.Result);
             Console.WriteLine("-------------------------");
         });
        Console.ReadLine();
    }
}

结果:

 Task.CompletedTask:

这个属性看起来像是拿到我们之前执行完成的Task,对吧?所以我的想法是构造许多Task,然后之后再调这个方法,得到之前构造中的Task中的一个:

static void Main(string[] args)
{
    Task.Run(() => { Console.WriteLine("task1:" + Task.CurrentId); });
    Task.Run(() => { Console.WriteLine("task2:" + Task.CurrentId); });
    //等待上面Task完成
    Thread.Sleep(100);
    Console.WriteLine("Task.CompletedTask id:" + Task.CompletedTask.Id);
    Console.ReadLine();
}

但是执行结果总是不尽人意:

无论如何执行多少次,或者增加多少个task,总是得不到之前执行完成的Task。为什么呢?

来看看源码吧:

可以看到,如果是第一次调用这个属性,那么它会新建一个已经完成的Task,之后再调用也会返回同样的Task实例,和你执行的其他task没有任何关系,那么拿到这个Task有什么用呢?

参考:https://stackoverflow.com/questions/44096253/await-task-completedtask-for-what/44096289

这篇文章解释的很好,就是如果你想构造一个异步方法,但是现在又没有可以执行的异步逻辑,而又为了以后不为了新增异步逻辑而将同步方法重构为异步方法,那么就使用await Task.CompletedTask这个属性,当做一个占位符,说明这里将来会有异步逻辑的加入。所以这个属性是为了提升程序可扩展性而使用的。

Task.CreationOptions:

上面初始化Task时已经说过。

Task.IsCompleted:

static void Main(string[] args)
{
   Task task = Task.Run(() => 
   {
       Thread.Sleep(50);
   });
   int count = 0;
   while (true)
   {
        if (count == 5 && task.IsCompleted)
        {
            Console.WriteLine("task已执行完成");
            break;
        }
        else
        {
            Console.WriteLine("在等待task完成之前先做其他事情");
            Thread.Sleep(10);
            if (count == 5)
            {
                count = 0;
            }
            count++;
        }
    }
    Console.ReadLine();
}

结果:

这个方法用于判断当前task是否已经执行完成,相较于其他的如Wait这类必须在等待task完成前阻塞线程的方法比起来,该属性的好处是线程可以留出时间给到其他程序,不用完全关注这个task是否完成,而是在需要的时候再去看它是否完成。

Task.IsCanceled:

我们先来看一下task会因异常中断而把IsCanceled属性置为true吗:

static void Main(string[] args)
{
    Task task = new Task(() =>
    {
        throw new Exception();
    });
    Thread.Sleep(100);
    Console.WriteLine("task因异常中断,task.IsCanceled值是:" + task.IsCanceled);
    Console.ReadLine();
}

结果:

不会,那么我们从源码中对这个属性的标记描述可以知道它会在什么时候被设置为true:

那就来试一下吧:

①在task开始执行之前标记cancellation:

static void Main(string[] args)
{

    var tokenSource = new CancellationTokenSource();
    CancellationToken ct = tokenSource.Token;
    Task task = new Task(() => { }, ct);
    //marked for cacellation before in started executing
    tokenSource.Cancel();
    task.Start();
    Console.WriteLine("task.IsCanceled:" + task.IsCanceled);
    Console.ReadLine();
}

不出所料:

②在程序中抛出OperationCanceledException或调用CancellationToken.ThrowIfCancellationRequested():

static void Main(string[] args)
{
    var tokenSource = new CancellationTokenSource();
    CancellationToken ct = tokenSource.Token;
    Task task = new Task(() => 
    {
        throw new OperationCanceledException();
        //or ct.ThrowIfCancellationRequested();
    }, ct);
    task.Start();
    tokenSource.Cancel();
    Thread.Sleep(10);
    Console.WriteLine("task.IsCanceled:" + task.IsCanceled);
    Console.ReadLine();
}

结果:

Task.IsFaulted:

随着task抛出未处理异常,在IsFaulted为真的情况下,Status会被设置为Faulted,Exception会记录异常。

static void Main(string[] args)
{
    Task task = new Task(() =>
    {
        throw new FileNotFoundException();
    });
    task.Start();
    task.ContinueWith((t) =>
    {
        Console.WriteLine("task.IsFaulted: " + t.IsFaulted);
        Console.WriteLine("task.Exception is null ? " + t.Exception == null);
        Console.WriteLine("task.Status:" + t.Status);
    });
    Console.ReadLine();
}

结果:

Task.Exception:

这个属性用于记录task抛出的异常,用IsFaulted那个例子即可:

Task.AsyncState:

最开始尝试直接执行一个task然后调用这个属性为null无果后,通过查看源码,得知可以从Task.Factory.FromAsync去设置这个值:

这一步可以看到,AsyncState的值依赖于一个私有属性m_stateObject:

这个私有属性又依赖state,通过一步一步最后找到了调用方:

所以,就可以尝试用一下这个方法为这个属性赋值了:

static void Main(string[] args)
{
    Task task = Task.Factory.StartNew(() => { });
    Func<AsyncCallback, object, IAsyncResult> beginMethod = (asyncCallback, obj) => 
    {
        Console.WriteLine("beginMethod obj: " + obj);
        return task;
    };
    Action<IAsyncResult> endMethod = (asyncResult) => {};
    object state = "test asyncState"; 
    Task task1 = Task.Factory.FromAsync(beginMethod, endMethod, state);
    Console.WriteLine("task1 asyncState:" + task1.AsyncState);
    Console.ReadLine();
}

不出所料:

好了,Task的属性说完了,现在来看一下它所包含的方法,几乎每个方法都有重载,其实只用看它重载方法中的一个就好了,其他就看场景选择:

void Start()

Starts the Task, scheduling it for execution to the current TaskScheduler.使用当前TaskScheduler去执行当前Task.

static void Main(string[] args)
{
    Task task = new Task(() => 
    {
        Console.WriteLine("Current taskScheduler:" + TaskScheduler.Current.Id);
    });
    task.Start();
    Console.ReadLine();
}

结果:

如果你不想使用默认的Scheduler去执行这个task,还可以使用带TaskScheduler参数的Start去自定义Scheduler。

void Wait() //Waits for the Task to complete execution.等待task执行完成。

static void Main(string[] args)
{
    Task task = Task.Run(() => 
    {
        Thread.Sleep(1000);
    });
    task.Wait();
    Console.WriteLine("当前task状态:" + task.Status);
    Console.ReadLine();
}

结果:

使用这个方法,会阻塞式的等待task完成,如果想稍微灵活点,可以考虑使用带timeout参数的Wait方法。

void RunSynchronously()//Runs the Task synchronously on the current TaskScheduler.使用scheduler同步运行当前Task.

注意:

使用该方法应保证task从未被执行过。

根据MSDN介绍:

Ordinarily, tasks are executed asynchronously on a thread pool thread and do not block the calling thread. Tasks executed by calling the RunSynchronously() method are associated with the current TaskScheduler and are run on the calling thread. If the target scheduler does not support running this task on the calling thread, the task will be scheduled for execution on the schedule, and the calling thread will block until the task has completed execution

也就是说,使用这个方法会有两种方式同步执行task:

①直接使用当前线程执行

②另起一个线程执行,但会阻塞当前线程,相当于Start+Wait.

看看例子:

static void Main(string[] args)
{
    Task task = new Task(() => 
    {
        Console.WriteLine("使用RunSynchronously task会同步执行");
        Thread.Sleep(1000);
    });
    task.RunSynchronously();
    Console.WriteLine("当前task状态:" + task.Status);
    Console.ReadLine();
}

结果:

Task ContinueWith(Action<Task> continuationAction) //Creates a continuation that executes asynchronously when the target Task completes. 当task执行完成继续执行continuationAction。

static void Main(string[] args)
{
    Task task = new Task(() => 
    {
        Console.WriteLine("正在学习ContinueWith方法 " + 
                          Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(1000);
    });
    task.Start();
    Console.WriteLine("taskId:" + task.Id + ",task1 instance:" + task.GetHashCode());
    Task task1 = task.ContinueWith((t) =>
    {
        Console.WriteLine("我正在使用task继续执行任务 " + 
                          Thread.CurrentThread.ManagedThreadId);
    });
    Console.WriteLine("task1Id:" + task1.Id + ",task1 instance:" + task.GetHashCode());
    Console.ReadLine();
}

结果:

通过结果可以知道:

①继续执行的任务和原task不被同一个线程执行

②虽是同一个task在继续执行任务,但ID不同了

ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext)

C# Task.ConfigureAwait方法能来做什么?_CookieYangK的博客-CSDN博客_configureawait,这个文章有说到。

YieldAwaitable Yield()

对于官方解释,没有看太懂,大概意思好像是使用Yield,如果SynchronizationContext非null,那么await Task.Yield()之后代码会被列为优先级低的代码执行(依然会同步执行,只是暂时执行其他代码).如果是null,那么await Task.Yield()会被异步执行。

这篇文章有相应解释,大概也印证了我看到的逻辑:

c# - When would I use Task.Yield()? - Stack Overflow

从代码看一下吧:

static void Main(string[] args)
{
    TestYield();
    Console.WriteLine("main thread:" + Thread.CurrentThread.ManagedThreadId);
    Console.ReadLine();
}
public async static void TestYield()
{
    //Dosomething important
    Thread.Sleep(500);
    Console.WriteLine("TestYiled1 thread is " + Thread.CurrentThread.ManagedThreadId);
    Console.WriteLine(SynchronizationContext.Current == null);
    await Task.Yield();
    //Unimportant
    Thread.Sleep(500);
    Console.WriteLine("TestYiled2 thread is " + Thread.CurrentThread.ManagedThreadId);
}

结果:

果然,在SynchronizationContext为null的情况,后续代码在新的上下文中被执行。

Task WhenAll(params Task[] tasks) //Creates a task that will complete when all of the Task objects in an array have completed. 建立一个task,等待所有task完成时该task完成

如果你觉得一个一个处理等待task太麻烦,那么就可以使用这个方法去收集所有你要等待的task统一处理就好,就比如说,我有3个计算题要计算,我不需要你做完一个回复一次,这样没有必要,我只需要所有计算题做完就好:

static void Main(string[] args)
{
    Task task1 = Task.Run(() => 
    {
        int calc = 1 + 1; 
    });
    Task task2 = Task.Run(() => 
    {
        int calc = 2 + 2;
    });
    Task task3 = Task.Run(() =>
    {
        int calc = 3 + 3;
    });
    Task task4 = Task.WhenAll(task1, task2,task3);
    task4.Wait();
    if (task4.IsCompleted)
    {
        Console.WriteLine("task1、task2、task3 is completed calc");
    }
        Console.ReadLine();
    }

结果:

Task<Task> WhenAny(params Task[] tasks)//Creates a task that will complete when any of the supplied tasks have completed. 建立一个task,等待任务中的任何一个任务完成时完成。

举个例子,你开启了3个抢票任务,有个抢票成功就可以通知你了,就可以利用这个方法:

static void Main(string[] args)
{
    Task task1 = Task.Run(() => 
    {
        Thread.Sleep(100);
        Console.WriteLine(" buy a ticket");
    });
    Task task2 = Task.Run(() => 
    {
        Thread.Sleep(200);
        Console.WriteLine(" buy a ticket");
    });
    Task task3 = Task.Run(() =>
    {
        Thread.Sleep(300);
        Console.WriteLine(" buy a ticket");
    });
    Task task4 = Task.WhenAny(task1, task2,task3);
    task4.Wait();
    if (task4.IsCompleted)
    {
         Console.WriteLine("Buy a ticket to complete~");
    }
         Console.ReadLine();
    }
}

结果:

bool WaitAll(Task[] tasks, TimeSpan timeout)//Waits for all of the provided cancellable Task objects to complete execution within a specified time interval.在指定时间等待所有的task完成。

static void Main(string[] args)
{
    Task task1 = Task.Run(() => 
    {
        Thread.Sleep(1000);
    });
    Task task2 = Task.Run(() => 
    {
        Thread.Sleep(1000);
    });
    Task task3 = Task.Run(() =>
    {
         Thread.Sleep(1000);
    });
    Task[] tasks = { task1, task2, task3 };
    bool isCompleted = Task.WaitAll(tasks, TimeSpan.FromMilliseconds(2000));
    if (isCompleted)
    {
         Console.WriteLine("All of task is exeute completed");
    }
    Console.ReadLine();
 }

结果:

Task<TResult> Run<TResult>(Func<Task<TResult>> function)//Queues the specified work to run on the ThreadPool and returns a proxy for the Task(TResult) returned by function. 将任务放到线程池中排队等待运行,并且返回一个Task<TResult>的代理task.

总之,Run方法就是拿来创建并执行任务的。

static void Main(string[] args)
{
    Func<Task<string>> func = () =>
    {
         Task<string> task = Task.Run(() => { return "0.0"; });
         return task;
    };
    Task<string> task1 = Task.Run(func);
    Console.WriteLine(task1.Result);
    Console.ReadLine(); 
}

结果:

Task<TResult> FromResult<TResult>(TResult result)//Creates a Task`1 that's completed successfully with the specified result.//就是把TResult的值作为返回值task.Result的值。

通过这篇文章可以知道这个方法的一些作用:

https://stackoverflow.com/questions/19568280/what-is-the-use-for-task-fromresulttresult-in-c-sharp

There are two common use cases I've found:

  1. When you're implementing an interface that allows asynchronous callers, but your implementation is synchronous.//可使用await Task.FromResult;设计一个异步方法,但方法内部是同步的
  2. When you're stubbing/mocking asynchronous code for testing.//做异步测试
public async static void TestYield()
{
    await Task<string>.FromResult("0.0");
}

Task<TResult> FromException<TResult>(Exception exception)//Creates a Task`1 that's completed with a specified exception.建立一个有指定异常的task.

看文章:

c# - Correct usage of return Task.FromException - Stack Overflow

static void Main(string[] args)
{
    Task task = TestYield();
    Console.WriteLine(task.Exception);
    Console.ReadLine();
}
public  static Task TestYield()
{
    return Task.FromException<string>(new Exception("Something err"));
}

结果:

Task<TResult> FromCanceled<TResult>(CancellationToken cancellationToken)\\ Creates a Task that's completed due to cancellation with a specified cancellation token. 创建一个由于指定cancellation token取消而完成的task。

与FromException类似。

Task Delay(int millisecondsDelay)//Creates a task that completes after a time delay.创建延迟指定时间完成的task.

static void Main(string[] args)
{
    Task task = Task.Delay(1000);
    task.Wait();
    if (task.IsCompleted)
    {
        Console.WriteLine("task is execute completed");
    }
    Console.ReadLine();
}

结果:

至此,Task中所有的继承、属性、方法都做了一遍了解,知道了基础用法,但还有很多方法不知道具体场景,这可能需要在以后在相应场景使用的了再回过头来补充。虽然思考场景是深入认识这个类的一个很好的方式,但无奈不能花费过多的时间在这一件事上,因为还有太多了不认识正等待着我去认识。

  • 13
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值