线程(Thread、ThreadPool)

线程的定义我想大家都有所了解,这里我就不再复述了。我这里主要介绍.NET Framework中的线程(Thread、ThreadPool)。

.NET Framework中的线程分为两类:1.前台线程;2.后台线程。

1.前台线程

class Program
{
static void Main(string[] args)
{
Console.WriteLine("=====Thread=====");
TestThread();
Console.WriteLine("主线程执行完毕");
} public static void TestThread()
{
Thread thread = new Thread(PrintNum);
thread.Start();
} public static void PrintNum()
{
Thread.Sleep();
for (int i = ; i < ; i++)
Console.WriteLine(i);
}
}

运行结果

从运行结果可以看出,主线程虽然执行完毕了,但是并没有退出程序,而是等待子线程执行完毕后,退出程序。

2.后台线程

class Program
{
static void Main(string[] args)
{
Console.WriteLine("=====ThreadPool=====");
  ThreadPool.QueueUserWorkItem(new WaitCallback(PrintNum));
  Console.WriteLine("主线程执行完毕");
} public static void PrintNum(object obj)
{
Thread.Sleep();
for (int i = ; i < ; i++)
Console.WriteLine(i);
}
}

运行结果

从运行结果可以看出,主线程运行完毕后,就直接退出了程序,没有等待子线程。

总结:

1.前台线程:主线程执行完毕后,会等待所有子线程执行完毕后,才退出程序。

2.后台线程:主线程执行完毕后,直接退出程序,不论子线程是否执行完毕。

3.推荐:多线程的操作,推荐使用线程池线程而非新建线程。因为就算只是单纯的新建一个线程,这个线程什么事情也不做,都大约需要1M的内存空间来存储执行上下文数据结构,并且线程的创建与回收也需要消耗资源,耗费时间。而线程池的优势在于线程池中的线程是根据需要创建与销毁,是最优的存在。但是这也有个问题,那就是线程池线程都是后台线程,主线程执行完毕后,不会等待后台线程而直接结束程序。所以下面就要引出.NET Framework4.0提供的Task,来解决此类问题。

Task

Task是.NET Framework4.0提供的新的操作线程池线程的封装类。它提供:等待、终止、返回值...优化线程操作的功能。

1.定义

Task 对象是一种的中心思想 基于任务的异步编程模式 首次引入.NET Framework  中。 因为由执行工作 Task 对象通常上异步执行一个线程池线程而不是以同步方式在主应用程序线程中,您可以使用 Status 属性,以及 IsCanceled, ,IsCompleted, ,和 IsFaulted 属性,以此来确定任务的状态。

以上MSDN中对Task的定义,从“异步执行一个线程池线程”可以得出Task的后台实现是通过线程池线程实现。

2.补充

Task的性能要优于ThreadPool。

1)ThreadPool的代码将以先进先出的算法存储在全局队列中,并且多个工作者线程之间竞争同一个同步锁。(这就Task性能优于ThreadPool的第一个原因)

2)Task的代码将以先进后出的算法存储在本地队列中,工作者线程执行本地队列中的代码没有同步锁的限制(这是Task性能优于ThreadPool的第二个原因),并且当工作者线程2空闲并且工作者线程1忙碌时,工作者线程2会尝试从工作者线程1(或者别的忙碌的工作者线程)的本地队列尾部“偷”任务,并会获取一个同步锁,不过这种行为很少发生。

3)简单调用

class Program
{
static void Main(string[] args)
{
TestSimpleTask();
} public static void TestSimpleTask()
{
Console.WriteLine("=====Task====="); //直接创建
Task task2 = new Task(() =>
{
Thread.Sleep();
for (int i = ; i < ; i++)
{
Console.WriteLine(i);
}
}); //如果你想测试超时等待后,任务是否会继续执行。就替换下面的代码
task2.Start();
task2.Wait(); Console.WriteLine("主程序执行完毕"); /*测试超时
task2.Start();
task2.Wait(1000); Console.WriteLine("主程序执行完毕");
Console.ReadLine();
*/
}
}

task.Wati(时间);这个方法可以确定等待任务的执行时间,当超过规定的时间后将不再等待,直接运行之后的代码,但是任务的代码仍然在后台运行,如果想超过等待时间就停止任务的执行,你需要看下文深入学习。(这个方法提供了极大的方便。如果在.NET Framework2.0中,实现类似的功能,需要定义一个时间全局变量,然后在线程中不断的循环判断。)

3.深入学习

Task(Action, CancellationToken, TaskCreationOptions)

以上是MSDN中Task(不包含输入参数与返回值)最复杂的构造函数,包含2个重要的参数CancellationToken、TaskCreationOptions,下面将详细介绍CancellationToken、TaskCreationOptions的意义以及运用。

a.CancellationToken(取消标记)

该类用来检测任务是否被取消。需要与System.Threading.CancellationTokenSource配合使用,CancellationTokenSource主动停止任务。(CancellationToken虽然有检测任务是否停止的属性,但是一旦CancellationTokenSource调用了Cancel()方法,那么任务将立即停止运行,也就是说任务中的任何代码都不会被执行)

场景:主线程抛出异常,在异常处理中停止任务的执行。

class Program
{
static void Main(string[] args)
{
TestCancellationTokenTask();
} public static void TestCancellationTokenTask()
{
CancellationTokenSource cts = new CancellationTokenSource(); try
{
Task task = Task.Factory.StartNew(() =>
{
for (int i = ; i < ; i++)
{
//当任务取消时,这段检测代码将永远不会被执行,因为任务已经被取消了
if (cts.Token.IsCancellationRequested)
{
Console.WriteLine("=====Task=====");
Console.WriteLine("任务被取消");
break;
}
else
{
Console.WriteLine("=====Task=====");
Console.WriteLine("子线程打印:{0}", i);
Thread.Sleep();
}
}
}, cts.Token); for (int i = ; i < ; i++)
{
if (i == )
{
Console.WriteLine("=====Main=====");
Console.WriteLine("主线程抛出异常");
throw new Exception("测试");
} Console.WriteLine("=====Main=====");
Console.WriteLine("主线程打印:{0}", i);
Thread.Sleep();
} task.Wait();
}
catch
{
cts.Cancel();
}
Console.WriteLine(cts.IsCancellationRequested);
}
}

注意:主线程抛出异常,无论任务是否被显示取消,都会停止运行。

b.TaskCreationOptions(任务创建选项)

以下是MSDN中关于TaskCreationOptions的枚举值,具体的运用还是要根据实际情况。下面介绍一下AttachedToParent的用法(第5、第6,实际的运用还需要多参考大神的运用)

// 默认
.None // 将任务放入全局队列中(任务将以先到先出的原则被执行)
.PreferFairness // 告诉TaskScheduler,线程可能要“长时间运行”
.LongRunning // 将一个Task与它的父Task关联
.AttachedToParent // Task以分离的子任务执行
.DenyChildAttach // 创建任务的执行操作将被视为TaskScheduler.Default默认计划程序
.HideScheduler // 强制异步执行添加到当前任务的延续任务
.RunContinuationsAsynchronously 场景:将多个任务关联为父子任务
class Program
{
static void Main(string[] args)
{
TestTaskCreationOptionsTask();
} public static void TestTaskCreationOptionsTask()
{
StringBuilder sb = new StringBuilder(); Task parent = new Task(() =>
{
new Task(() =>
{
sb.Append("任务1");
}).Start(); new Task(() =>
{
sb.Append("任务2");
}).Start(); new Task(() =>
{
sb.Append("任务3");
}).Start(); //parent任务的调用线程停止5s,这样“任务1”、“任务2”、“任务3”就有时间执行完毕了。
//这里用来测试,当任务彼此之间是独立的,那么只有这种方式,控制台才会有打印。
//Thread.Sleep(5000);
}); /*
Task parent = new Task(() =>
{
new Task(() =>
{
sb.Append("任务1");
}, TaskCreationOptions.AttachedToParent).Start(); new Task(() =>
{
Thread.Sleep(3000);
sb.Append("任务2");
}, TaskCreationOptions.AttachedToParent).Start(); new Task(() =>
{
Thread.Sleep(3000);
sb.Append("任务3");
}, TaskCreationOptions.AttachedToParent).Start();
});
*/ parent.Start();
parent.Wait();
Console.WriteLine(sb.ToString());
}
}

说明:

1.Task的创建如果没有TaskCreationOptions.AttachedToParent,那么任务彼此之间是独立的,parent任务不会等待“任务1”、“任务2”、“任务3”都执行完毕后,才认为已经结束。

2.Task的创建如果有TaskCreationOptions.AttachedToParent,那么父任务必须等待子任务都执行完毕后,才会认为任务结束。

注意:虽然“任务1”、“任务2”、“任务3”是在parent任务中创建,但是可以分配在不同的线程池本地队列中,由不同的线程调用并且任务间并不是串行执行,而是并行执行。

c.返回值

以上介绍的所有内容,Task都没有返回值,但是在实际运用中,Task执行完之后,返回一个值供外部代码使用,这种情况很常见。

class Program
{
static void Main(string[] args)
{
TestReturnValueTask();
} public static void TestReturnValueTask()
{
Task<int> task = new Task<int>(num =>
{
Thread.Sleep();
return (int)num + ;
}, );
task.Start(); Console.WriteLine(task.Result);
}
}

注意:当Task的返回值被调用时,主线程会等待Task执行完毕,才会退出程序。所以这里没有调用task.Wait();

d.TaskContinuationOptions(任务延续选项)

有时候,我们需要在一个任务结束后,执行另一个任务。两个任务之间有先后顺序,这个时候,就需要用到TaskContinuationOptions。

以下是MSDN中关于TaskContinuationOptions的枚举值,这里只列出了部分(4.5新增的枚举只根据MSDN的机器翻译确实不太理解如何运用,还是需要花些时间测试,这里就不列出了,怕误导读者)

// 默认
.None // 将任务放入全局队列中(任务将以先到先出的原则被执行)
.PreferFairness // 告诉TaskScheduler,线程可能要“长时间运行”,需要为任务创建一个专用线程,而不是排队让线程池线程来处理
.LongRunning // 将一个Task与它的父Task关联
.AttachedToParent // 希望执行第一个Task的线程,执行ContinueWith任务
.ExecuteSynchronously // 第一个任务没有完成,执行后续任务
.NotOnRanToCompletion // 第一个任务没有失败,执行后续任务
.NotOnFaulted // 第一个任务没有取消,执行后续任务
.NotOnCanceled // 只有当第一个任务取消,执行后续任务
.OnlyOnCanceled // 只有当第一个任务失败,执行后续任务
.OnlyOnFaulted // 只有当第一个任务完成,执行后续任务
.OnlyOnRanToCompletion
class Program
{
static void Main(string[] args)
{
TestContinueTask();
} public static void TestContinueTask()
{
/*
Task<int> task = new Task<int>(num =>
{
return (int)num + 1;
}, 100); Task taskContinue = task.ContinueWith(c =>
{
Console.WriteLine(c.Result);
}, TaskContinuationOptions.OnlyOnRanToCompletion); task.Start();
taskContinue.Wait();
*/ CancellationTokenSource cts = new CancellationTokenSource();
Task<int> task = new Task<int>(num =>
{
return (int)num + ;
}, , cts.Token); Task taskContinue = task.ContinueWith(c =>
{
Console.WriteLine("任务Task被取消了");
}, TaskContinuationOptions.OnlyOnCanceled); task.Start();
cts.Cancel();
taskContinue.Wait();
}
}

写到这里,关于Task的介绍已经结束。但是Task的内容还有很多:异常处理(AggregateException)、取消通知委托(CancellationToken的Register方法)这些内容就留给读者自己去学习了。

有时候,可能需要一次性创建多个任务,并且这些任务共享相同的状态。那么我们就可以通过任务工厂来创建。

TaskFactory

任务工厂,顾名思义,用来创建任务的工厂。在大多数情况下,不需要实例化一个新 TaskFactory<TResult> 实例。 可以使用静态Task<TResult>.Factory 属性,它返回一个工厂对象,将使用默认值。 然后可以调用其方法来启动新任务或定义任务延续。

代码

class Program
{
static void Main(string[] args)
{
TestTaskFactory();
} public static void TestTaskFactory()
{
TaskFactory<DateTime> factory = new TaskFactory<DateTime>(); Task<DateTime>[] tasks = new Task<DateTime>[]
{
factory.StartNew(() =>
{
return DateTime.Now.ToUniversalTime();
}), factory.StartNew(() =>
{
Thread.Sleep();
return DateTime.Now.ToUniversalTime();
}), factory.StartNew(() =>
{
return DateTime.Now.ToUniversalTime();
})
}; StringBuilder sb = new StringBuilder();
foreach (Task<DateTime> task in tasks)
sb.AppendFormat("{0}\t", task.Result); Console.WriteLine(sb.ToString());
}
}

注意:任务可以分配在不同的线程池本地队列中,由不同的线程调用并且任务间并不是串行执行,而是并行执行。

Parallel

并行,让多个线程池线程并行工作。由于是并行执行,所以有一点需要注意:工作项彼此之间必须可以并行执行!

class Program
{
static void Main(string[] args)
{
TestParallel();
} public static void TestParallel()
{
Parallel.For(, , i =>
{
Console.WriteLine(i);
}); List<int> lists = new List<int> { , , , , , , , , , };
Parallel.ForEach(lists, i =>
{
Console.WriteLine(i);
});
}
}

说明:

1.Parallel.For效率高于Parallel.Foreach,所以当For与Foreach都可以时,推荐使用For。

2.上面的代码,运行For时,你可能会发现数字是有顺序的打印出来,给人一种串行执行的错觉,你可以断点调试你的代码,会发现确实有多个线程在运行代码。

3.Parallel.For()、Parallel.Foreach()还有一些重载方法,大家可以结合实际情况使用,这里就不复述了。

感谢大家的耐心阅读。

from:https://www.cnblogs.com/color-wolf/p/4850869.html

线程(Thread,ThreadPool)、Task、Parallel的更多相关文章

  1. 异步和多线程,委托异步调用,Thread,ThreadPool,Task,Parallel,CancellationTokenSource

    1 进程-线程-多线程,同步和异步2 异步使用和回调3 异步参数4 异步等待5 异步返回值 5 多线程的特点:不卡主线程.速度快.无序性7 thread:线程等待,回调,前台线程/后台线程, 8 th ...

  2. c#中@标志的作用 C#通过序列化实现深表复制 细说并发编程-TPL 大数据量下DataTable To List效率对比 【转载】C#工具类:实现文件操作File的工具类 异步多线程 Async .net 多线程 Thread ThreadPool Task .Net 反射学习

    c#中@标志的作用   参考微软官方文档-特殊字符@,地址 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/toke ...

  3. 从Thread,ThreadPool,Task, 到async await 的基本使用方法解读

    记得很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题 ...

  4. Thread,ThreadPool,Task, 到async await 的基本使用方法和理解

    很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题吧. ...

  5. 异步多线程 Thread ThreadPool Task

    一.线程 Thread ThreadPool 线程是Windows任务调度的最小单位,线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针.程序计数器等),但代码区是共享的,即不同的线程可以 ...

  6. Thread&ThreadPool、Parallel、Async和Await用法总结

    1.线程和线程池Thread&ThreadPool //线程初始化时执行方法可以带一个object参数,为了传入自定义参数,所以执行需单独调用用于传参. Console.WriteLine(& ...

  7. Thread,ThreadPool,Task

    线程分为前台和后台.比如我们直接new一个Thread这就是前台线程. 前台线程一定会执行. 比如我们创建2个线程:1号,2号,同时执行,假设1号是主线程,1执行完了,依旧会等待2执行完成,整个程序才 ...

  8. .NET多线程(Thread,ThreadPool,Task,Async与Await)

    .NET多线程是什么? 进程与线程 进程是一种正在执行的程序. 线程是程序中的一个执行流. 多线程是指一个程序中可以同时运行多个不同的线程来执行不同的任务. .NET中的线程 Thread是创建和控制 ...

  9. .net 多线程 Thread ThreadPool Task

    先准备一个耗时方法 /// <summary>/// 耗时方法/// </summary>/// <param name="name">< ...

  10. 浅析C#中的Thread ThreadPool Task和async/await

    .net 项目中不可避免地要与线程打交道,目的都是实现异步.并发.从最开始的new Thread()入门,到后来的Task.Run(),如今在使用async/await的时候却有很多疑问. 先来看一段 ...

随机推荐

  1. python基础之 time,datetime,collections

    1.time模块 python中的time和datetime模块是时间方面的模块 time模块中时间表现的格式主要有三种: 1.timestamp:时间戳,时间戳表示的是从1970年1月1日00:00 ...

  2. [py]pandas数据统计学习

    pandas.core.base.DataError: No numeric types to aggregate错误规避 我没有去解决这个问题, 而用填充0规避了这个问题 统计 聚合 d = [ { ...

  3. Go语言 切片长度和容量

    package main import "fmt" func main() { s := []int{2, 3, 5, 7, 11, 13} printSlice(s) // Sl ...

  4. 原生js 基于canvas写一个简单的前端 截图工具

    先看效果 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <titl ...

  5. Qt QLabel 大小随内容自动变化 && 内容填充整个label空间

    图1:label的本身大小 图2:给label设置文字,不做任何别的设置 ui->label->setText(QObject::tr("current font is %1&q ...

  6. vi / vim 命令集合

    vim的命令太多了,不常用就会忘记,所以我决定把vim的各种命令整理下来,包括vim的插入删除.光标移动.多窗口编辑.复制粘贴.查找替换.以及一些常用命令 删除操作 dd 删除当前行 ndd      ...

  7. delphi 的一些注意点和知识点

    关于Delphi中产生的文件    编辑阶段: pas/单元文件,dpk/组件包文件,dpr/工程文件,dfm/窗体文件    编译阶段: dcu/单元编译文件,dcp/Delphi Compile ...

  8. 容器技术研究-Kubernetes基本概念

    最近在研究容器技术,作为入门,基本概念必须搞明白,今天整理一下Kubernetes的基本概念. 一.什么是Kubernetes Kubernetes(k8s)是自动化容器操作的开源平台,这些操作包括部 ...

  9. json传实体到后台接受

    转自https://www.cnblogs.com/threadj/p/10535760.html 我用第一种是没问题的,第二种还不行,再研究一下 1.json参数为json字符串 var data ...

  10. IDEA Failed to load dx.jar

    IDEA-177053 Android app crashes on build "Failed to load dx.jar" Error:Android Pre Dex: [c ...