知识需要不断积累、总结和沉淀,思考和写作是成长的催化剂

梯子

一、任务Task1、启动任务2、阻塞延续3、任务层次结构4、枚举参数5、任务取消6、任务结果7、异常二、并行Parallel1、Parallel.For()、Parallel.ForEach()2、Parallel.For3、Parallel.Invoke()4、PLinq三、异步等待AsyncAwait1、简单使用2、优雅3、最后

一、任务Task

System.Threading.Tasks在.NET4引入,前面线程的API太多了,控制不方便,而ThreadPool控制能力又太弱,比如做线程的延续、阻塞、取消、超时等功能不太方便,所以Task就抽象了线程功能,在后台使用ThreadPool

1、启动任务

可以使用TaskFactory类或Task类的构造函数和Start()方法,委托可以提供带有一个Object类型的输入参数,所以可以给任务传递任意数据,还漏了一个常用的Task.Run

TaskFactory taskFactory = new TaskFactory();
taskFactory.StartNew(() => 
{
    Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
Task.Factory.StartNew(() =>
{
    Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
Task task = new Task(() =>
{
    Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
task.Start();

只有Task类实例方式需要Start()去启动任务,当然可以RunSynchronously()来同步执行任务,主线程会等待,就是用主线程来执行这个task任务

Task task = new Task(() =>
{
    Thread.Sleep(10000);
    Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
task.RunSynchronously();

2、阻塞延续

在Thread中我们使用join来阻塞等待,在多个Thread时进行控制就不太方便。Task中我们使用实例方法Wait阻塞单个任务或静态方法WaitAll和WaitAny阻塞多个任务

var task = new Task(() =>
{
    Thread.Sleep(5*1000);
    Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
var task2 = new Task(() =>
{
    Thread.Sleep(10 * 1000);
    Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
task.Start();
task2.Start();
//task.Wait();//单任务等待
//Task.WaitAny(task, task2);//任何一个任务完成就继续
Task.WaitAll(task, task2);//任务都完成才继续

如果不希望阻塞主线程,实现当一个任务或几个任务完成后执行别的任务,可以使用Task静态方法WhenAll和WhenAny,他们将返回一个Task,但这个Task不允许你控制,将会在满足WhenAll和WhenAny里任务完成时自动完成,然后调用Task的ContinueWith方法,就可以在一个任务完成后紧跟开始另一个任务

Task.WhenAll(task, task2).ContinueWith((t) =>
{
    Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});

Task.Factory工厂中也存在类似ContinueWhenAll和ContinueWhenAny

3、任务层次结构

不仅可以在一个任务结束后执行另一个任务,也可以在一个任务内启动一个任务,这就启动了一个父子层次结构

var parentTask = new Task(()=> 
{
    Console.WriteLine($"parentId={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
    Thread.Sleep(5*1000);
    var childTask = new Task(() =>
    {
        Thread.Sleep(10 * 1000);
        Console.WriteLine($"childId={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}")
    });
    childTask.Start();
});
parentTask.Start();

如果父任务在子任务之前结束,父任务的状态为WaitingForChildrenToComplete,当子任务也完成时,父任务的状态就变为RanToCompletion,当然,在创建任务时指定TaskCreationOptions枚举参数,可以控制任务的创建和执行的可选行为

4、枚举参数

简单介绍下创建任务中的TaskCreationOptions枚举参数,创建任务时我们可以提供TaskCreationOptions枚举参数,用于控制任务的创建和执行的可选行为的标志

  1. AttachedToParent:指定将任务附加到任务层次结构中的某个父级,意思就是建立父子关系,父任务必须等待子任务完成才可以继续执行。和WaitAll效果一样。上面例子如果在创建子任务时指定TaskCreationOptions.AttachedToParent,那么父任务wait时也会等子任务的结束
  2. DenyChildAttach:不让子任务附加到父任务上
  3. LongRunning:指定是长时间运行任务,如果事先知道这个任务会耗时比较长,建议设置此项。这样,Task调度器会创建Thread线程,而不使用ThreadPool线程。因为你长时间占用ThreadPool线程不还,那它可能必要时会在线程池中开启新的线程,造成调度压力
  4. PreferFairness:尽可能公平的安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。实际通过把任务放到线程池的全局队列中,让工作线程去争抢,默认是在本地队列中。

另一个枚举参数是ContinueWith方法中的TaskContinuationOptions枚举参数,它除了拥有几个和上面同样功能的枚举值外,还拥有控制任务的取消延续等功能

  1. LazyCancellation:在延续取消的情况下,防止延续的完成直到完成先前的任务。什么意思呢?
CancellationTokenSource source = new CancellationTokenSource();
source.Cancel();
var task1 = new Task(() => 
{
    Console.WriteLine($"task1 id={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
var task2 = task1.ContinueWith(t =>
{
    Console.WriteLine($"task2 id={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
},source.Token);
var task3 = task2.ContinueWith(t =>
{
    Console.WriteLine($"task3 id={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
task1.Start();

上面例子我们企图task1->task2->task3顺序执行,然后通过CancellationToken来取消task2的执行。结果会是怎样呢?结果task1和task3会并行执行(task3也是会执行的,而且是和task1并行,等于原来的一条链变成了两条链),然后我们尝试使用LazyCancellation,

var task2 = task1.ContinueWith(t =>
{
    Console.WriteLine($"task2 id={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
},source.Token,TaskContinuationOptions.LazyCancellation,TaskScheduler.Current);

这样,将会在task1执行完成后,task2才去判断source.Token,为Cancel就不执行,接下来执行task3就保证了原来的顺序

  1. ExecuteSynchronously:指定应同步执行延续任务,比如上例中,在延续任务task2中指定此参数,则task2会使用执行task1的线程来执行,这样防止线程切换,可以做些共有资源的访问。不指定的话就随机,但也能也用到task1的线程
  2. NotOnRanToCompletion:延续任务必须在前面任务非完成状态下执行
  3. OnlyOnRanToCompletion:延续任务必须在前面任务完成状态才能执行
  4. NotOnFaulted,OnlyOnCanceled,OnlyOnFaulted等等

5、任务取消

在上篇使用Thread时,我们使用一个变量isStop标记是否取消任务,这种访问共享变量的方式难免会出问题。task中提出CancellationTokenSource类专门处理任务取消,常见用法看下面代码注释

CancellationTokenSource source = new CancellationTokenSource();//构造函数中也可指定延迟取消
//注册一个取消时调用的委托
source.Token.Register(() =>
{
    Console.WriteLine("当前source已经取消,可以在这里做一些其他事情(比如资源清理)...");
});
var task1 = new Task(() => 
{
    while (!source.IsCancellationRequested)
    {
        Console.WriteLine($"task1 id={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
    }
},source.Token);
task1.Start();
//source.Cancel();//取消
source.CancelAfter(1000);//延时取消

6、任务结果

让子线程返回结果,可以将信息写入到线程安全的共享变量中去,或则使用可以返回结果的任务。使用Task的泛型版本Task<TResult>,就可以定义返回结果的任务。Task是继承自Task的,Result获取结果时是要阻塞等待直到任务完成返回结果的,内部判断没有完成则wait。通过TaskStatus属性可获得此任务的状态是启动、运行、异常还是取消等

var task = new Task<string>(() =>
{
     return "hello ketty";
});
task.Start();
string result = task.Result;

7、异常

可以使用AggregateException来接受任务中的异常信息,这是一个聚合异常继承自Exception,可以遍历获取包含的所有异常,以及进行异常处理,决定是否继续往上抛异常等

var task = Task.Factory.StartNew(() =>
{
    var childTask1 = Task.Factory.StartNew(() =>
    {
        throw new Exception("childTask1异常...");
    },TaskCreationOptions.AttachedToParent);
    var childTask12= Task.Factory.StartNew(() =>
    {
        throw new Exception("childTask2异常...");
    }, TaskCreationOptions.AttachedToParent);
});
try
{
    try
    {
        task.Wait();
    }
    catch (AggregateException ex)
    {
        foreach (var item in ex.InnerExceptions)
        {
            Console.WriteLine($"message{item.InnerException.Message}");
        }
        ex.Handle(x =>
        {
            if (x.InnerException.Message == "childTask1异常...")
            {
                return true;//异常被处理,不继续往上抛了
            }
            return false;
        });
    }
}
catch (Exception ex)
{
    throw;
}

二、并行Parallel

1、Parallel.For()、Parallel.ForEach()

在.NET4中,另一个新增的抽象的线程时Parallel类。这个类定义了并行的for和foreach的静态方法。Parallel.For()和Parallel.ForEach()方法多次调用一个方法,而Parallel.Invoke()方法允许同时调用不同的方法。首先Parallel是会阻塞主线程的,它将让主线程也参与到任务中
Parallel.For()类似于for允许语句,并行迭代同一个方法,迭代顺序没有保证的

ParallelLoopResult result = Parallel.For(0, 10, i =>
{
    Console.WriteLine($"{i} task:{Task.CurrentId} thread:{Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine(result.IsCompleted);

也可以提前中断Parallel.For()方法。For()方法的一个重载版本接受Action<int,parallelloopstate style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">类型参数。一般不使用,像下面这样,本想大于5就停止,但实际也可能有大于5的任务已经在跑了。可以通过ParallelOptions传入允许最大线程数以及取消Token等

ParallelLoopResult result = Parallel.For(0, 10, new ParallelOptions() { MaxDegreeOfParallelism = 8 },(i,loop) =>
{
    Console.WriteLine($"{i} task:{Task.CurrentId} thread:{Thread.CurrentThread.ManagedThreadId}");
    if (i > 5)
    {
        loop.Break();
    }
});

2、Parallel.For<TLocal>

For还有一个高级泛型版本,相当于并行的聚合计算

ParallelLoopResult For<TLocal>(int fromInclusive, int toExclusive, Func<TLocal> localInit, Func<int, ParallelLoopState, TLocal, TLocal> body, Action<TLocal> localFinally);

像下面这样我们求0…100的和,第三个参数更定一个种子初始值,第四个参数迭代累计,最后聚合

int totalNum = 0;
Parallel.For<int>(0, 100, () => { return 0; }, (current, loop, total) =>
{
    total += current;
    return total;
}, (total) =>
{
    Interlocked.Add(ref totalNum, total);
});

上面For用来处理数组数据,ForEach()方法用来处理非数组的数据任务,比如字典数据继承自IEnumerable的集合等

3、Parallel.Invoke()

Parallel.Invoke()则可以并行调用不同的方法,参数传递一个Action的委托数组

Parallel.Invoke(() => { Console.WriteLine($"方法1 thread:{Thread.CurrentThread.ManagedThreadId}"); }
    , () => { Console.WriteLine($"方法2 thread:{Thread.CurrentThread.ManagedThreadId}"); }
    , () => { Console.WriteLine($"方法3 thread:{Thread.CurrentThread.ManagedThreadId}"); });

4、PLinq

Plinq,为了能够达到最大的灵活度,linq有了并行版本。使用也很简单,只需要将原始集合AsParallel就转换为支持并行化的查询。也可以AsOrdered来顺序执行,取消Token,强制并行等

var nums = Enumerable.Range(0, 100);
var query = from n in nums.AsParallel()
            select new
            {
                thread=$"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}"
            };

三、异步等待AsyncAwait

异步编程模型,可能还需要大篇幅来学习,这里先介绍下基本用法,内在本质需要用ILSpy反编译来看,以后可能要分专题总结。文末先给几个参考资料,有兴趣自己阔以先琢磨琢磨鸭

1、简单使用

这是.NET4.5开始提供的一对语法糖,使得可以较简便的使用异步编程。async用在方法定义前面,await只能写在带有async标记的方法中,任何方法都可以增加async,一般成对出现,只有async没有意义,只有await会报错,请先看下面的示例

private static async void AsyncTest()
{
    //主线程执行
    Console.WriteLine($"before await ThreadId={Thread.CurrentThread.ManagedThreadId}");
    TaskFactory taskFactory = new TaskFactory();
    Task task = taskFactory.StartNew(() =>
    {
        Thread.Sleep(3000);
        Console.WriteLine($"task ThreadId={Thread.CurrentThread.ManagedThreadId}");
    });
    await task;//主线程到这里就返回了,执行主线程任务
    //子线程执行,其实是封装成委托,在task之后成为回调(编译器功能  状态机实现) 后面相当于task.ContinueWith()
    //这个回调的线程是不确定的:可能是主线程  可能是子线程  也可能是其他线程,在winform中是主线程
    Console.WriteLine($"after await ThreadId={Thread.CurrentThread.ManagedThreadId}");
}

一般使用async都会让方法返回一个Task的,像下面这样复杂一点的

private static async Task<string> AsyncTest2()
{
    Console.WriteLine($"before await ThreadId={Thread.CurrentThread.ManagedThreadId}");
    TaskFactory taskFactory = new TaskFactory();
    string x = await taskFactory.StartNew(() =>
      {
          Thread.Sleep(3000);
          Console.WriteLine($"task ThreadId={Thread.CurrentThread.ManagedThreadId}");
          return "task over";
      });     Console.WriteLine($"after await ThreadId={Thread.CurrentThread.ManagedThreadId}");
    return x;
}

通过var reslult = AsyncTest2().Result;调用即可。但注意如果调用Wait或Result的代码位于UI线程,Task的实际执行在其他线程,其需要返回UI线程则会造成死锁,所以应该Async all the way

2、优雅

从上面简单示例中可以看出异步编程的执行逻辑:主线程A逻辑->异步任务线程B逻辑->主线程C逻辑
异步方法的返回类型只能是void、Task、Task。示例中异步方法的返回值类型是Task,通常void也不推荐使用,没有返回值直接用Task就是

上一篇也大概了解到如果我们要在任务中更新UI,需要调用Invoke通知UI线程来更新,代码看起来像下面这样,在一个任务后去更新UI

private void button1_Click(object sender, EventArgs e)
{
    var ResultTask = Task.Run(() => {
        Thread.Sleep(5000);
        return "任务完成";
    });
    ResultTask.ContinueWith((r)=> 
    {
        textBox1.Invoke(() => {
            textBox1.Text = r.Result;
        });
    });
}

如果使用async/await会看起来像这样,是不是优雅了许多。以看似同步编程的方式实现异步

private async void button1_Click(object sender, EventArgs e)
{
    var t = Task.Run(() => {
        Thread.Sleep(5000);
        return "任务完成";
    });
    textBox1.Text = await t;
}

https://www.cnblogs.com/OpenCoder/p/4434574.html
https://www.cnblogs.com/zhaoshujie/p/11192036.html
https://devblogs.microsoft.com/pfxteam/asyncawait-faq/
https://www.cnblogs.com/zh7791/p/9951478.html

3、最后

在.NET 4.5中引入的Async和Await两个新的关键字后,用户能以一种简洁直观的方式实现异步编程。甚至都不需要改变代码的逻辑结构,就能将原来的同步函数改造为异步函数。
在内部实现上,Async和Await这两个关键字由编译器转换为状态机,通过System.Threading.Tasks中的并行类实现代码的异步执行。

字数有点多了,我的能力也就高考作文800字能写的出奇好。看了很多异步编程,脑袋有点炸,等消化后再输出一次,技艺不足,只能用输出倒逼输入了,下一篇会是线程安全集合、锁问题、同步问题,基于事件的异步模式等

Search the fucking web
Read the fucking maunal

.NET进阶篇06-async异步、thread多线程3的更多相关文章

  1. 02: tornado进阶篇

    目录:Tornado其他篇 01: tornado基础篇 02: tornado进阶篇 03: 自定义异步非阻塞tornado框架 04: 打开tornado源码剖析处理过程 目录: 1.1 自定制t ...

  2. .NET进阶篇06-async异步、thread多线程4

    知识需要不断积累.总结和沉淀,思考和写作是成长的催化剂 梯子 一.锁1.lock2.Interlocked3.Monitor4.SpinLock5.Mutex6.Semaphore7.Events1. ...

  3. C#异步和多线程以及Thread、ThreadPool、Task区别和使用方法

    本文的目的是为了让大家了解什么是异步?什么是多线程?如何实现多线程?对于当前C#当中三种实现多线程的方法如何实现和使用?什么情景下选用哪一技术更好? 第一部分主要介绍在C#中异步(async/awai ...

  4. c# 异步( Async ) 不是多线程

    c# 异步( Async ) 不是多线程   误解 async 在调试 xxxxAsync() 方法的时候,常常会看到调试器界面中会多出一些线程,直觉上误认为 Async 冠名的函数是多线程. 对于 ...

  5. spring boot(17)-@Async异步

    验证码的异步机制 上一篇讲过可以用邮件发验证码,通常我们在某网站发验证码时,首先会提示验证码已发送,请检查邮箱或者短信,这就是图中的1和3.然而此时查看邮箱或短信可能并没有收到验证码,往往要过几秒种才 ...

  6. PowerBuilder编程新思维2:嵌入(Thread多线程)

    PowerBuilder编程新思维2:嵌入(Thread多线程) 在PB中使用多线程,在网上有大量的文章介绍.不过深入研究并试着给出更易用的模型的,目前还只有"路人甲cw"的一篇& ...

  7. Node.js进阶篇-koa、钩子函数、websocket、嵌入式开发

    代码地址如下:http://www.demodashi.com/demo/12932.html 一.简介     koa是由Express原班人马打造的,致力于成为一个更小.更富有表现力.更健壮的We ...

  8. 在 .NET 4.0 下编写扩展代码以支持 async 异步编程

    微软在C# 5中引入了async.await这两个异步编程的关键字,要使用这两个关键字需要你的IDE支持C#5.0语法,也就意味着你需要使用VS 2012版本以上IDE,或者在Vs2010卸载其编译器 ...

  9. Oracle RMAN 学习:演练进阶篇

    Oracle RMAN 学习:演练进阶篇 5 Rman备份演练进阶篇 5.1 是否选择增量备份 Backup命令生成的备份集中只备份了那些使用了的数据块,备份集实际大小已经较目标数据库的数据文件小了很 ...

  10. Spring Boot (18) @Async异步

    通常我们在某网站发送邮件验证码时,首先会提示验证码已发送,然而此时可能没有收到验证码,过几秒种才真正的收到.如果是同步会先验证发送是否成功然后再通知,如果是异步可以先通知用户已发送,并释放请求,然后再 ...

随机推荐

  1. python编程系列---global的使用注意点

    global用法虽然很简单,当在函数中要修改全局变量时要使用到,但也要注意一个小问题,看下面的代码: 调用append()方法,只是修改,并不是赋值操作,不需要global提前声明 temp_num ...

  2. chrome devtools tip(1)--调试伪类

    开发中我们经常遇到,添加些focus,hover事件,样式,但当我们去打开 chrome devtools,浮动上去的时候,然后准备去改变样式的时候,结果由于光标移动了,样式不见了,非常不方便调试,其 ...

  3. JDK1.8 新特性详解

    一  引言 现在java 10都已经出来了,而自己对java 8的一些新特性都不了解,很是惭愧,而且许多面试都有问到java8的新特性,借此博客好好学习这些新特性 二  新特性 1 default关键 ...

  4. The usage of Markdown---引用

    目录 1. 序言 2. 引用与嵌套引用 3. 列表中的引用 更新时间:209.09.14 1. 序言   在本篇,我们来仔细谈一下Markdown的引用. 2. 引用与嵌套引用   在Markdown ...

  5. 解决SpringBatch/Cloud Task的SafeMode下的报错问题

    问题描述 一般公司都有DBA,DBA极有可能开启了Safe mode,也就是不支持不带索引条件过滤的update操作. 而Spring Batch /Cloud Task就有一张表 JOB_SEQ或者 ...

  6. Udp 异步通信(三)

    转自:https://blog.csdn.net/zhujunxxxxx/article/details/44258719 1)服务端 using System; using System.Colle ...

  7. CentOS 7 的root口令破解两种方法

    破解CentOS7的root口令 方法一: 第一步: 启动时任意键暂停启动 按-e-键进入编辑模式 第二步: 1.将光标移动至蓝框处linux16开头的行,添加内核参数 rd.break 2.按ctr ...

  8. SQL语句实现:当A列大于B列时选择A列否则选择B列,当B列大于C列时选择B列否则选择C列

    分享一道今天的面试题:SQL语句实现:数据库中有A B C三列,当A列大于B列时选择A列否则选择B列,当B列大于C列时选择B列否则选择C列 第一种:使用case when...then...else ...

  9. Docker应用部署

    MySQL: #拉取mysql镜像 docker pull centos/mysql--centos7 #创建容器 #-p 端口映射 -e添加环境变量MYSQL_ROOT_PASSWORD 是root ...

  10. mine:dp

    一个小的线性dp.方法很多,八仙过海各显神通. 我想讲一下我的: #include<cstdio> #define mod 1000000007 ];][][],n;//是不是雷,右边有没 ...