Thread

在.NET中最早提供的控制线程类型的类型:System.Threading.Thread类。使用该类型可以直观地创建、控制和结束线程。下面是一个简单的多线程程序:

static void Main(string[] args)
{
Console.WriteLine("进入多线程模式:"); for (int i = 0; i < 5; i++)
{
Thread t = new Thread(Work);
t.Start();//开启新线程
} Console.ReadKey();
} static void Work()
{
Console.WriteLine($"开始工作的线程ID:{Thread.CurrentThread.ManagedThreadId} ");
Thread.Sleep(1000);//假如处理任务耗时1s
Console.WriteLine($"结束工作的线程ID:{Thread.CurrentThread.ManagedThreadId} ");
}

 该段代码是在主线程中创建5个线程,每个线程进行独立的工作互不干涉。代码执行结果如下:

Thread中提供了很多控制线程的方法,例如:终止本线程(Abort)、暂停(Suspend)、恢复(Resume)、阻塞调用线程(Join)等等。

ThreadPool

Thread是.NET Framework 1.0中推出实现多线程的方案,它是直接从计算机系统里抓取的线程,这就存在很多问题,例如:频繁的创建和销毁线程、无节制的创建线程等。所以微软在.NET Framework 2.0中推出ThreadPool(线程池),它是给开发人员提供一个线程容器,在使用多线程的时从容器中抓取线程。

internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("进入多线程模式:"); for (int i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(Work));
} Console.ReadKey();
} static void Work(object stateInfo)
{
Console.WriteLine($"开始工作的线程ID:{Thread.CurrentThread.ManagedThreadId} ");
//
Console.WriteLine($"结束工作的线程ID:{Thread.CurrentThread.ManagedThreadId} ");
} }

上面这段就是使用ThreadPool实现多线程,代码执行结果如下:

Task

Task是从 .NET Framework 4 推出的实现多线程的方法,Task 的使用非常简单,一行代码就可以开始一个异步任务,代码如下:

        static void Main(string[] args)
{
Console.WriteLine($"Task 示例,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}");
Task.Run(() => Console.WriteLine($"Task 开启的线程,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}"));
Console.ReadKey();
}

向新创建的Task中传递参数的示例代码:

static void Main(string[] args)
{
Console.WriteLine($"Task 示例,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}"); string temp = "Tanyongjun"; Task t = new Task(obj => Console.WriteLine($"Task 开启的线程,传递进来的参数:{obj},当前线程的ID:{Thread.CurrentThread.ManagedThreadId}"), temp);
t.Start(); Console.ReadKey();
}

返回一个 int 类型结果的示例代码:

static void Main(string[] args)
{
Console.WriteLine($"Task 示例,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}"); Task<int> t = new Task<int>(() => {
int sum = 0;
for (int i = 0; i < 10; i++)
{
sum += i;
}
return sum;
});
t.Start();
Console.WriteLine($"使用Task开启新线程计算的结果:{t.Result}"); Console.ReadKey();
}

使用 TaskFactory 开始异步任务

static void Main(string[] args)
{
List<Task<int>> tasks = new List<Task<int>>();
TaskFactory factory = new TaskFactory();
tasks.Add(factory.StartNew<int>(() => { return 123; }));
tasks.Add(factory.StartNew<int>(() => { return 456; }));
foreach (var item in tasks)
{
Console.WriteLine($"返回值:{item.Result}");
} Console.Read();
}

上面的代码使用 TaskFactory 创建并运行了两个异步任务,同时把这两个任务加入了任务列表 tasks 中,然后使用foreach遍历出执行的结果。代码执行效果如下:

用Task来模拟一个项目开发的场景

        static void Main(string[] args)
{ Console.WriteLine($"项目经理启动一个项目。线程ID:{Thread.CurrentThread.ManagedThreadId} ");
Console.WriteLine($"前期准备工作。线程ID:{Thread.CurrentThread.ManagedThreadId} ");
Console.WriteLine($"开始编程。线程ID:{Thread.CurrentThread.ManagedThreadId} "); List<Task> tasks = new List<Task>();//存放新开启的Task
tasks.Add(Task.Run(() => Coding("张三", "后端")));
tasks.Add(Task.Run(() => Coding("李四", "前端")));
tasks.Add(Task.Run(() => Coding("王五", "测试"))); // 阻塞当前线程,等着某一个任务执行完成后,才进入下一行。
Task.WaitAny(tasks.ToArray());
Console.WriteLine($"************** 项目里程碑。。线程ID:{Thread.CurrentThread.ManagedThreadId} **************"); //等待1秒之后,执行某个动作
//Task.WaitAll(tasks.ToArray(), 1000);
//Console.WriteLine($"************** 已经等待了1秒啦。。线程ID:{Thread.CurrentThread.ManagedThreadId} **************"); // 阻塞当前线程,等着全部任务完成后,才进行下一行。
Task.WaitAll(tasks.ToArray());
Console.WriteLine($"部署到正式环境,上线使用。线程ID:{Thread.CurrentThread.ManagedThreadId} "); Console.Read();
} private static void Coding(string name, string project)
{
Console.WriteLine($"Coding {name} 开始 {project}。线程ID:{Thread.CurrentThread.ManagedThreadId}。");
int temp = 0;
for (int i = 0; i < 1000000000; i++)
{
temp += i;
}
Console.WriteLine($"Coding {name} 结束 {project}。线程ID:{Thread.CurrentThread.ManagedThreadId}。"); }

代码执行效果如下:

Task 内部提供多种多样的基于队列的链式任务管理方法,通过使用这些快捷方式,可以让异步队列有序的执行,例如:ContinueWith(),ContinueWhenAll(),ContinueWhenAny(),WaitAll(),WaitAny(),WhenAll(),WhenAny()。

线程中的异常处理

static void Main(string[] args)
{
try
{
TaskFactory taskFactory = new TaskFactory();
List<Task> taskList = new List<Task>();
for (int i = 0; i < 5; i++)
{
int k = i;
Action<object> act = t => {
Thread.Sleep(1000);
if (k == 3)
throw new Exception("出现异常啦!");
Console.WriteLine($"程序正常 -- {k}。");
};
taskList.Add(taskFactory.StartNew(act, k));
}
}
catch (AggregateException aex)
{
foreach (var item in aex.InnerExceptions)
{
Console.WriteLine(item.Message);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
} Console.ReadKey();
}

  上面的代码执行结果:

在vs调试的时候能看到出现异常,但在从执行结果上看不到异常信息。这是因为线程里面的异常是抓不到的,它已经脱离try/catch 的范围。所以在新的线程内部添加try/catch ,也就是Action 具体操作内容加上try/catch,让Action不会出现异常。

上面的代码改动如下:

static void Main(string[] args)
{
try
{
TaskFactory taskFactory = new TaskFactory();
List<Task> taskList = new List<Task>();
for (int i = 0; i < 5; i++)
{
int k = i;
Action<object> act = t => {
try
{
Thread.Sleep(1000);
if (k == 3)
throw new Exception("出现异常啦!");
Console.WriteLine($"程序正常 -- {k}。");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
} };
taskList.Add(taskFactory.StartNew(act, k));
}
}
catch (AggregateException aex)
{
foreach (var item in aex.InnerExceptions)
{
Console.WriteLine(item.Message);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
} Console.ReadKey();
}

  执行效果:

线程安全

        static void Main(string[] args)
{
TaskFactory taskFactory = new TaskFactory();
List<Task> taskList = new List<Task>();
List<int> intList = new List<int>();
int count = 0; for (int i = 0; i < 10000; i++)
{
int k = i;
taskList.Add(taskFactory.StartNew(() =>
{
count += 1;
intList.Add(k);
}));
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine($"count={count}");
Console.WriteLine($"intList中元素的个数={intList.Count()}"); Console.ReadKey();
}

 执行效果:  

从执行后的结果来看两个都不是10000,这是因为有的线程丢失了。
多个线程同时操作一个变量(包括:都能访问的局部变量、全局变量、数据库中的一个值),就会有某个操作会被覆盖的可能。

想要解决上面出现的问题,我们可以通过加lock 来实现。

        //private:防止外面使用;static:保障全局唯一;readonly:不能改动;object:引用类型
private static readonly object threadLock = new object();
static void Main(string[] args)
{
TaskFactory taskFactory = new TaskFactory();
List<Task> taskList = new List<Task>();
List<int> intList = new List<int>();
int count = 0; for (int i = 0; i < 10000; i++)
{
int k = i;
taskList.Add(taskFactory.StartNew(() =>
{ lock (threadLock)
{
// 加lock 后就能保障任意时刻只有一个线程来执行该代码段
count += 1;
intList.Add(k);
}
//lock (this)
//{
// // 使用this 要注意每次实例化是不同的锁,同一个实例是相同的锁。
// // 但是这个实例别人也能访问到,别人也能锁定。 所以一般不要使用
//} }));
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine($"count={count}");
Console.WriteLine($"intList中元素的个数={intList.Count()}"); Console.ReadKey();
}

  执行效果:

使用lock 能解决线程线程安全,因为使用lock后只有一个线程可以进去,没有并发。虽然能解决问题,但是牺牲了性能。所以在使用lock来解决线程冲突的问题是要尽量缩小lock的范围。这种问题的最佳解决方案是,进行数据拆分,避免冲突。

await/async

在.NET Framework4.5框架、C#5.0语法中,新增加了async和await两个关键字。
async 用来标记一个方法为异步方法,方法体内需结合 await 关键字使用。异步方法命名规则通常以Async结尾。await 关键字只能在异步方法中使用。它出现在 Task 前面。
async和await 一般都是成对出现的,只有async 是没有意义的,只有await 就会报错。

static void Main(string[] args)
{ Console.WriteLine("执行前Main的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
TestAsync();
Console.WriteLine("执行结束Main的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString()); Console.ReadKey();
} private static async void TestAsync()
{
Console.WriteLine("执行前TestAsync的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString()); await Task.Run(()=> {
Console.WriteLine("执行 Action 中Sleep之前 的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
Thread.Sleep(3000);
Console.WriteLine("执行 Action 中Sleep之后 的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
}); Console.WriteLine("执行结束TestAsync的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
}

  执行效果:

 

  

.net基础—多线程(二)的更多相关文章

  1. java基础-多线程二

    java基础-多线程二 继承thread和实现Runnable的多线程每次都需要经历创建和销毁的过程,频繁的创建和销毁大大影响效率,线程池的诞生就可以很好的解决这一个问题,线程池可以充分的利用线程进行 ...

  2. java基础-多线程应用案例展示

    java基础-多线程应用案例展示 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.两只熊,100只蜜蜂,蜜蜂每次生产的蜂蜜量是1,罐子的容量是30,熊在罐子的蜂蜜量达到20的时候 ...

  3. Linux基础练习题(二)

    Linux基础练习题(二) 1.复制/etc/skel目录为/home/tuer1,要求/home/tuser1及其内部文件的属组和其它用户均没有任何访问权限. [root@www ~]# cp -r ...

  4. Bootstrap <基础十二>下拉菜单(Dropdowns)

    Bootstrap 下拉菜单.下拉菜单是可切换的,是以列表格式显示链接的上下文菜单.这可以通过与 下拉菜单(Dropdown) JavaScript 插件 的互动来实现. 如需使用下列菜单,只需要在 ...

  5. RequireJS基础(二)

    上一篇是把整个jQuery库作为一个模块.这篇来写一个自己的模块:选择器. 为演示方便这里仅实现常用的三种选择器id,className,attribute. RequireJS使用define来定义 ...

  6. Servlet基础(二) Servlet的生命周期

    Servlet基础(二) Servlet的生命周期 Servlet的生命周期可以分为三个阶段: 1.初始化阶段 2.响应客户请求阶段 3.终止阶段 Servlet的初始化阶段 在下列时刻Servlet ...

  7. 好好写,好好干-PHP基础(二)

    hi 好久没写,昨儿一写,感觉还是有人看的,至少是有一两个评论的~~好好干! 每天需要坚持的就那么4件事儿:写这个,学一点法语,看会儿书,锻炼.单身狗也有好处. 1.PHP 一.PHP基础(二) 1. ...

  8. php基础篇-二维数组排序 array_multisort

    原文:php基础篇-二维数组排序 array_multisort 对2维数组或者多维数组排序是常见的问题,在php中我们有个专门的多维数组排序函数,下面简单介绍下: array_multisort(a ...

  9. MySQL基础(二)——DDL语句

    MySQL基础(二)--DDL语句 1.什么是DDL语句,以及DDL语句的作用 DDL语句时操作数据库对象的语句,这些操作包括create.drop.alter(创建.删除.修改)数据库对象. 2.基 ...

  10. Python 基础语法(二)

    Python 基础语法(二) --------------------------------------------接 Python 基础语法(一) ------------------------ ...

随机推荐

  1. golang json字符串合并操作

    用于两个json格式的字符串合并,当B向A合并时,共有的字段,将用B字段的值(伴随类型一起覆盖),非共有的,A的字段保留,B的字段新增. example代码: package main import ...

  2. Codeforces 1132E(大数据多重背包)

    题目链接 题意 给定背包容量$w$,体积分别为$1$到$8$的物体的数量求不超过背包容量的最大体积 思路 考虑将答案转化成$840 * x + y$的形式其中$840 = lcm(1-8), y &l ...

  3. 配置隐藏index.php

    .htaccess文件写入类容放到跟目录下就OK <IfModule mod_rewrite.c> Options +FollowSymlinks -Multiviews RewriteE ...

  4. VM部署服务后设置局域网内其他人访问

    第一种方式:虚拟机设置中,网络适配器选择桥接模式,此时虚拟机IP号段与局域网处于同一号段,局域网内其他人使用虚拟机IP+端口即可访问服务 将虚拟机IP设为静态IP,我的虚拟机系统为Ubuntu20.0 ...

  5. for循环axios套axios调用,同步调取

    1.function getsdd(){}事件 async/await把异步进行设置成同步进行 var url = '/api/runtime/form/save'; function checkAd ...

  6. LoadRunner参数和变量之间的转换

    这是用LoadRunner自定义监控Tomcat的脚本为基础而写的脚本.阐述了参数相互之间以及参数与变量之间复制传递原理.下面的代码注释是按照自己的理解写的,正确性不一定保证. Action() { ...

  7. 微信小程序图片和签名

    图片上传功能 chooseImage(e) { wx.chooseImage({ sizeType: ['original', 'compressed'], //可选择原图或压缩后的图片 source ...

  8. starlette.routing.NoMatchFound

    目前正在学习FastAPI, 目前是学习到了引入静态文件.这是我引入的本地文件的方式 url_for('/static', path='/imgs/favicon.ico') 只要启动服务,就会报错5 ...

  9. KingbaseES V8R3集群维护案例之---pcp_node_refresh应用

    案例说明: 在一次KingbaseES V8R3集群切换分析中,运维人员执行了pcp_node_refresh,导致集群发生了failover的切换.此文档对pcp_node_refresh工具做了应 ...

  10. (03-14) synopsys中工具介绍,VCS,DC,PT等

    https://blog.csdn.net/fangxiangeng/article/details/80981536 (1)Nlint 检查,spyglass (2)PT 静态时序检查 (3)Icc ...