ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C#线程:任务Task

2022-09-13 15:33:31  阅读:237  来源: 互联网

标签:Task Console C# 任务 task 线程 WriteLine


Task是一个更高级的抽象概念,它代表了一个并发操作,而该操作并不一定依赖线程来完成。Task是可以组合的(可以将它们通过延续(continuation)操作串联在一起)。它们可以使用线程池减少启动延迟,也可以通过TaskCompletionSource采用回调的方式避免多个线程同时等待I/O密集型操作。

Task类是Framework 4.0时作为并行编程库的组成部分引入的。然而它们后来经历了许多改进(通过使用等待器(awaiter)),从而在常见的并发场景中发挥了越来越大的作用。Task类也是C#异步功能的基础类型。

1.启动任务

启动一个基于线程的Task的最简单方式是使用Task.Run(Task类位于System.Threading.Tasks命名空间)静态方法。调用时只需传入一个Action委托:

Task.Run(()=> Console.WriteLine("foo"));

和下面的方式很相似:

new Thread (()=> Console.WriteLine("foo")).Start();

Task.Run会返回一个Task对象,它可以用于监控任务的执行过程。这一点与Thread对象不同。我们可以使用Task的Status属性来追踪其执行状态。

Wait方法

调用Task的Wait方法可以阻塞当前方法,直到任务完成,这和调用线程对象的Join方法类似:

Task task = Task.Run (() =>
{
    Console.WriteLine ("Task started");
    Thread.Sleep (2000);
    Console.WriteLine ("Foo");
});
Console.WriteLine (task.IsCompleted);  // False
task.Wait();

可以在Wait中指定一个超时时间和取消令牌(可选)来提前终止等待状态。

长任务

默认情况下,CLR会将任务运行在线程池线程上,这种线程非常适合执行短小的计算密集的任务。如果要执行长时间阻塞的操作(如上面的例子),则可以按照以下方式避免使用线程池线程:

Task task = Task.Factory.StartNew(()=>......,TaskCreationOptions.LongRunning);

2.返回值

Task有一个泛型子类Task<TResult>,它允许任务返回一个值。如果在调用Task.Run时传入一个Func<TResult>委托(或者兼容的Lambda表达式)替代Action就可以获得一个Task<TResult>对象,通过查询Result属性就可以获得任务的返回值。如果当前任务还没有执行完毕,调用该属性会阻塞当前线程,直至任务结束。

Task<int> task = Task.Run(() =>
{
    Console.WriteLine("Foo");
    return 3;
});
int result = task.Result; 

可以将Task理解为一个“未来值”,它封装了Result并将在以后生效。

3.异常

任务可以方便地传播异常,这和线程是截然不同的。因此,如果任务中的代码抛出一个未处理异常,那么调用Wait()或者访问Task的Result属性时,该异常就会被重新抛出。

Task task = Task.Run (() => { throw null; });
try 
{
    task.Wait();
}
catch (AggregateException aex)
{
    if (aex.InnerException is NullReferenceException)
        Console.WriteLine ("Null!");
    else
        throw;
}

使用Task的IsFaulted和IsCanceled属性可以在不抛出异常的情况下检测出错的任务。如果IsCanceled为true,则说明任务抛出了OperationCanceledException;如果IsFaulted为true,则说明任务抛出了其他类型的异常,通过Exception属性可以了解该异常的信息。

4.延续

延续会告知任务在完成后继续执行后续的操作。延续通常由回调方法实现,该方法会在操作完成后执行。
以下是计算素数的例子:

Task<int> primeNumberTask = Task.Run (() =>
    Enumerable.Range (2, 3000000).Count (n => 
        Enumerable.Range (2, (int)Math.Sqrt(n)-1).All (i => n % i > 0)));

var awaiter = primeNumberTask.GetAwaiter();
awaiter.OnCompleted (() => 
{
    int result = awaiter.GetResult();
    Console.WriteLine (result);      
});

调用任务的GetAwaiter方法将返回一个awaiter对象。这个对象的OnCompleted方法告知先导任务(primeNumberTask)当它执行完毕(或者出现错误)时调用一个委托。将延续附加到一个已执行完毕的任务上是完全没有问题的,此时,延续的逻辑将会立即执行。

如果先导任务出现错误,则延续代码调用awaiter.GetResult()时会重新抛出异常。对于非泛型任务,GetResult的返回值为void,这个函数的用途完全是为了重新抛出异常。

5.TaskCompletionSource类

另一种创建任务的方法是使用TaskCompletionSource,这种任务并非那种需要执行启动操作并在随后停止的任务;而是在操作结束或出错时手动创建的“附属”任务。这非常适用于I/O密集型的工作:它不但可以利用任务所有的优点(能够传递返回值、异常或延续),而且不需要在操作执行期间阻塞线程。

TaskCompletionSource的用法很简单,直接进行实例化即可。它包含一个Task属性,返回一个Task对象。
下面的例子会在等待5秒钟后输出42:

var tcs = new TaskCompletionSource<int>();
new Thread (() => 
{ 
    Thread.Sleep (5000); 
    tcs.SetResult (42); 
}).Start();

Task<int> task = tcs.Task;      
Console.WriteLine (task.Result);

6.Task.Delay方法

它是Task类的一个静态方法,是Thread.Sleep的异步版本,不会造成阻塞。

标签:Task,Console,C#,任务,task,线程,WriteLine
来源: https://www.cnblogs.com/nullcodeworld/p/16689242.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有