菜鸟之旅——初识.NET管理

返回值

  Thead是不可以再次回到值的,不过作为更高级的Task当然要弥补一下以此功能。

static void Main() {
    // GetDayOfThisWeek 运行在另外一个线程中
    var dayName = Task.Run<string>(() => { return GetDayOfThisWeek(); });
    Console.WriteLine("今天是:{0}",dayName.Result);
}

  .Net
Framework经历了过多版本的更动,可是它的框架没有太大的变型,包括了公共语言运行时(CLR)、基类库和.Net
Framework类库、公共语言专业和襄助的语言;

共享数据

  下边说了参数和重返值,我们来看一下线程之间共享数据的题材。

private static bool _isDone = false;    
static void Main(){
    new Thread(Done).Start();
    new Thread(Done).Start();
}

static void Done(){
    if (!_isDone) {
        _isDone = true; // 第二个线程来的时候,就不会再执行了(也不是绝对的,取决于计算机的CPU数量以及当时的运行情况)
        Console.WriteLine("Done");
    }
}

 管理 1

  线程之间可以由此static变量来共享数据。

  总结

  本篇博客就写到那吗,内容也大抵是田园里内容,也期待可以辅助到想入坑.Net的爱侣们。

内容索引

  公共语言专业

  很不满,我对这公共语言专业(CLS)也不了然,也只可以说说大概。

  .Net协理广大语言,有C#、VB等,每种语言必定带着和谐的特征,可是我们都可以透过编译在CLR下面跑,并且都足以与其他语言举办互操作,这都是因为具备语言都遵守了CLS;.NET
Framework将CLS定义为一组规则,所有.NET语言都应有坚守此规则才能创设与任何语言可互操作的应用程序,但要注意的是为了使各语言可以互操作,只好利用CLS所列出的功效对象,这个效率统称为与CLS兼容的功用。再往下的细节实现就不清楚了,把那一个也列在其后的求学计划当中吧。

传扬参数

static void Main() {
    new Thread(Go).Start("arg1"); // 没有匿名委托之前,我们只能这样传入一个object的参数

    new Thread(delegate(){  // 有了匿名委托之后...
        GoGoGo("arg1", "arg2", "arg3");
    });

    new Thread(() => {  // 当然,还有 Lambada
        GoGoGo("arg1","arg2","arg3");
    }).Start();

    Task.Run(() =>{  // Task能这么灵活,也是因为有了Lambda呀。
        GoGoGo("arg1", "arg2", "arg3");
    });
}

public static void Go(object name){
    // TODO
}

public static void GoGoGo(string arg1, string arg2, string arg3){
    // TODO
}

  入坑.Net
也曾经两年多了,既然在微软.Net 体系下混,对.Net
连串也亟需了解一下,当然这多少个知识也都是查看资料都可以查到的,那里根本是对友好所学的盘整,况且目前的学习有些闭门造车的含意,现在想写出来和我们大饱眼福一下,如若知道有错误,欢迎园友指正!

管理 2

  公共语言运行时(CLR)

  CLR是.Net Framework的根底内容,也是.Net程序的运转环境,可以将其当作一个在执行时管理代码的代办,它提供了内存管理、线程管理、代码执行、垃圾收集(GC)和长距离处理等大旨服务,并且还强制实施严苛的花色安全以及可增强安全性和可靠性的其它形式的代码准确性。

  C#仍旧其他各种语言编写的源代码通过编译器生成IL代码托管(IL也称托管代码),最终取得一个托管模块,一个或多个托管模块组成程序集(assembly)交给CLR运行,不过CLR仍然无法一向和操作系统(OS)间接互动,还亟需JIT引擎来进展“翻译”,变成总计机可以分辨的二进制代码交给操作系统执行。

  对了此处提到了CLR就只可以涉及托管代码非托管代码:

  托管代码 (managed
code)是由CLR(而不是一向由操作系统)执行的代码。托管代码应用程序可以收获公共语言运行库服务,例如自动垃圾回收、运行库类型检查和平安辅助等。这个服务帮扶提供单身于阳台和言语的、统一的托管代码应用程序行为。在托管执行环境中拔取托管代码及其编译,可以制止过多头名的造成安全黑洞和不平静程序的编程错误。同样,许多不可靠的计划性也自动的被提高了安全
性,例如
类型安全检查,内存管理和刑释解教无效对象。程序员可以花更多的生命力关注程序的应用逻辑设计并得以减去代码的编写量。这就象征更短的付出时间和更健壮的程序。

  非托管代码 (unmanaged
code)是指在国有语言运行库环境的外部,由操作系统直接实施的代码。非托管代码必须提供温馨的排泄物回收、类型检查、安全襄助等服务;它与托管代码不同,后者从国有语言运行库中拿走那一个劳动。

一个小例子认识async & await

static void Main(string[] args){
    Test(); // 这个方法其实是多余的, 本来可以直接写下面的方法
    // await GetName()  
    // 但是由于控制台的入口方法不支持async,所有我们在入口方法里面不能 用 await

    Console.WriteLine("Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId);
}

static async Task Test(){
    // 方法打上async关键字,就可以用await调用同样打上async的方法
    // await 后面的方法将在另外一个线程中执行
    await GetName();
}

static async Task GetName(){
    // Delay 方法来自于.net 4.5
    await Task.Delay(1000);  // 返回值前面加 async 之后,方法里面就可以用await了
    Console.WriteLine("Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId);
    Console.WriteLine("In antoher thread.....");
}

管理 3

        管理 4

async 和 await 出现在C#
5.0事后,给相互编程带来了成百上千的有益,特别是当在MVC中的Action也改为async之后,有点开首什么都是async的意味了。可是这也给我们编程埋下了部分隐患,有时候可能会暴发一些大家团结一心都不晓得怎么爆发的Bug,特别是假若连线程基础没有清楚的情景下,更不清楚怎样去处理了。这前天大家就来可以看看这两小兄弟和她们的二伯(Task)外祖父(Thread)们究竟有什么界别和特征,本文将会对Thread
到 Task 再到 .NET 4.5的 async和
await,这二种艺术下的互相编程作一个概括性的牵线包括:开启线程,线程结果回到,线程中止,线程中的万分处理等。

  基类库和.Net Framework

  基类库(NET Standard
Library)包含补助底层操作的一文山会海通用效率,覆盖了汇聚操作、线程襄助、代码生成、输入输出(IO)、映射和平安等领域的情节。此外,.Net Core也是基类库的落实,当然也有谈得来特殊的实现,并且与.Net
Framework不同,它是支撑跨平台的,详细学习会在连续的博客中分享。

  .Net Framework是基类库在windows操作系统下的贯彻,包含类库:数据库访问(ADO
.NET等)、XML辅助、目录服务(LDAP等)、正则表明式和音讯帮忙;并且还落实无数我们开发人士通常使用的应用程序开发技术:ASP
.NET技术、WinFroms技术和WPF技术等高档编程技术。

很是处理

  另外线程的异常,主线程可以捕获到么?

public static void Main(){
    try{
        new Thread(Go).Start();
    }
    catch (Exception ex){
        // 其它线程里面的异常,我们这里面是捕获不到的。
        Console.WriteLine("Exception!");
    }
}
static void Go() { throw null; }

  那么升级了的Task呢?

public static void Main(){
    try{
        var task = Task.Run(() => { Go(); });
        task.Wait();  // 在调用了这句话之后,主线程才能捕获task里面的异常

        // 对于有返回值的Task, 我们接收了它的返回值就不需要再调用Wait方法了
        // GetName 里面的异常我们也可以捕获到
        var task2 = Task.Run(() => { return GetName(); });
        var name = task2.Result;
    }
    catch (Exception ex){
        Console.WriteLine("Exception!");
    }
}
static void Go() { throw null; }
static string GetName() { throw null; }

线程安全

   我们先把地点的代码小小的调整一下,就领悟什么样是线程安全了。大家把Done方法中的两句话对换了须臾间地方。

private static bool _isDone = false;    
static void Main(){
    new Thread(Done).Start();
    new Thread(Done).Start();
    Console.ReadLine();
}

static void Done(){
    if (!_isDone) {
       Console.WriteLine("Done"); // 猜猜这里面会被执行几次?
        _isDone = true; 
    }
}

管理 5 

  上边这种状况不会一向暴发,不过一旦您运气好的话,就会中奖了。因为第一个线程还从未来得及把_isDone设置成true,第二个线程就进入了,而这不是我们想要的结果,在五个线程下,结果不是大家的预料结果,这就是线程不安全。

await 的原形

  await后的的举办各种 

 管理 6

     感谢 locus的指正, await
之后不会敞开新的线程(await
一贯不会敞开新的线程),所以地方的图是有好几题材的。

  await
不会开启新的线程,当前线程会一贯往下走直到遇见真正的Async方法(比如说HttpClient.GetStringAsync),这多少个措施的中间会用Task.Run或者Task.Factory.StartNew
去开启线程。也就是一旦艺术不是.NET为大家提供的Async方法,大家需要投机创办Task,才会真正的去创设线程

static void Main(string[] args)
{
    Console.WriteLine("Main Thread Id: {0}\r\n", Thread.CurrentThread.ManagedThreadId);
    Test();
    Console.ReadLine();
}

static async Task Test()
{
    Console.WriteLine("Before calling GetName, Thread Id: {0}\r\n", Thread.CurrentThread.ManagedThreadId);
    var name = GetName();   //我们这里没有用 await,所以下面的代码可以继续执行
    // 但是如果上面是 await GetName(),下面的代码就不会立即执行,输出结果就不一样了。
    Console.WriteLine("End calling GetName.\r\n");
    Console.WriteLine("Get result from GetName: {0}", await name);
}

static async Task<string> GetName()
{
    // 这里还是主线程
    Console.WriteLine("Before calling Task.Run, current thread Id is: {0}", Thread.CurrentThread.ManagedThreadId);
    return await Task.Run(() =>
    {
        Thread.Sleep(1000);
        Console.WriteLine("'GetName' Thread Id: {0}", Thread.CurrentThread.ManagedThreadId);
        return "Jesse";
    });
}

管理 7

  大家再来看一下这张图:

  管理 8

  1. 进入主线程起始实施
  2. 调用async方法,再次来到一个Task,注意这一个时候此外一个线程已经起头运行,也就是GetName里面的
    Task
    已经上马工作了
  3. 主线程继续往下走
  4. 管理,第3步和第4步是同时展开的,主线程并没有挂起等待
  5. 如果另一个线程已经进行完毕,name.IsCompleted=true,主线程依然不用挂起,直接拿结果就足以了。假使另一个线程还同有执行完毕,
    name.IsCompleted=false,那么主线程会挂起等待,直到回到结果得了。

只有async方法在调用前才能加await么?

static void Main(){
    Test();
    Console.ReadLine();
}

static async void Test(){
    Task<string> task = Task.Run(() =>{
        Thread.Sleep(5000);
        return "Hello World";
    });
    string str = await task;  //5 秒之后才会执行这里
    Console.WriteLine(str);
}

  答案很明确:await并不是针对于async的主意,而是指向async方法所重回给我们的Task,这也是为何所有的async方法都不可能不回到给我们Task。所以我们同样可以在Task前边也充裕await关键字,这样做实际是告诉编译器我急需等这一个Task的重回值或者等这一个Task执行完毕之后才能连续往下走。

不用await关键字,如何确认Task执行完毕了?

static void Main(){
    var task = Task.Run(() =>{
        return GetName();
    });

    task.GetAwaiter().OnCompleted(() =>{
        // 2 秒之后才会执行这里
        var name = task.Result;
        Console.WriteLine("My name is: " + name);
    });

    Console.WriteLine("主线程执行完毕");
    Console.ReadLine();
}

static string GetName(){
    Console.WriteLine("另外一个线程在获取名称");
    Thread.Sleep(2000);
    return "Jesse";
}

管理 9

Task.GetAwaiter()和await Task 的区别?

 管理 10

  • 增长await关键字之后,后边的代码会被挂起等待,直到task执行完毕有重临值的时候才会连续向下执行,这一段时间主线程会处于挂起状态。
  • GetAwaiter方法会重返一个awaitable的靶子(继承了INotifyCompletion.OnCompleted方法)咱们只是传递了一个委托进去,等task完成了就会执行这么些委托,可是并不会潜移默化主线程,上边的代码会立即实施。这也是干什么我们结果其中第一句话会是
    “主线程执行完毕”!

Task怎么着让主线程挂起等待?

  上边的右手是属于尚未挂起主线程的状态,和大家的await仍旧有某些区别,那么在得到Task的结果前怎么着挂起主线程呢?

static void Main(){
    var task = Task.Run(() =>{
        return GetName();
    });

    var name = task.GetAwaiter().GetResult();
    Console.WriteLine("My name is:{0}",name);

    Console.WriteLine("主线程执行完毕");
    Console.ReadLine();
}

static string GetName(){
    Console.WriteLine("另外一个线程在获取名称");
    Thread.Sleep(2000);
    return "Jesse";
}

  管理 11

Task.GetAwait()方法会给我们回去一个awaitable的目的,通过调用这一个目标的GetResult方法就会挂起主线程,当然也不是负有的处境都会挂起。还记得我们Task的特性么?
在一方始的时候就启动了另一个线程去实施这多少个Task,当我们调用它的结果的时候假若这一个Task已经执行完毕,主线程是不用等待可以直接拿其结果的,要是没有进行完毕这主线程就得挂起等待了。

await 实质是在调用awaitable对象的GetResult方法

static async Task Test(){
    Task<string> task = Task.Run(() =>{
        Console.WriteLine("另一个线程在运行!");  // 这句话只会被执行一次
        Thread.Sleep(2000);
        return "Hello World";
    });

    // 这里主线程会挂起等待,直到task执行完毕我们拿到返回结果
    var result = task.GetAwaiter().GetResult();  
    // 这里不会挂起等待,因为task已经执行完了,我们可以直接拿到结果
    var result2 = await task;     
    Console.WriteLine(str);
}

管理 12

到此截至,await就精神大白了,欢迎点评。Enjoy Coding! 🙂 

创建

static void Main(){
    new Thread(Go).Start();  // .NET 1.0开始就有的 
    Task.Factory.StartNew(Go); // .NET 4.0 引入了 TPL
    Task.Run(new Action(Go)); // .NET 4.5 新增了一个Run的方法
}

public static void Go(){
    Console.WriteLine("我是另一个线程");
}

  这之中需要专注的是,创造Thread的实例之后,需要手动调用它的Start方法将其启动。可是对于Task来说,StartNew和Run的同时,既会成立新的线程,并且会当即启动它。

Semaphore 信号量

  我骨子里不亮堂那么些单词应该怎么翻译,从官方的讲演来看,我们得以这么敞亮。它可以操纵对某一段代码或者对某个资源访问的线程的数额,超越这么些数量之后,此外的线程就得拭目以待,只有等前几日无线程释放了后头,下面的线程才能访问。这个跟锁有相似的机能,只可是不是总揽的,它同意一定数量的线程同时做客。

static SemaphoreSlim _sem = new SemaphoreSlim(3);    // 我们限制能同时访问的线程数量是3
static void Main(){
    for (int i = 1; i <= 5; i++) new Thread(Enter).Start(i);
    Console.ReadLine();
}

static void Enter(object id){
    Console.WriteLine(id + " 开始排队...");
    _sem.Wait();
    Console.WriteLine(id + " 开始执行!");          
    Thread.Sleep(1000 * (int)id);               
    Console.WriteLine(id + " 执行完毕,离开!");      
    _sem.Release();
}

 

  管理 13

在最起初的时候,前3个排队之后就立马进入实施,可是4和5,唯有等到无线程退出之后才足以实施。

线程池 

  线程的创制是相比较占用资源的一件业务,.NET
为我们提供了线程池来支援我们创设和管理线程。Task是默认会直接使用线程池,可是Thread不会。借使我们不采纳Task,又想用线程池的话,可以使用ThreadPool类。

static void Main() {
    Console.WriteLine("我是主线程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
    ThreadPool.QueueUserWorkItem(Go);

    Console.ReadLine();
}

public static void Go(object data) {
    Console.WriteLine("我是另一个线程:Thread Id {0}",Thread.CurrentThread.ManagedThreadId);
}

  要解决地方遭逢的题目,大家就要用到锁。锁的档次有独占锁,互斥锁,以及读写锁等,我们这里就简单演示一下独占锁。

private static bool _isDone = false;
private static object _lock = new object();
static void Main(){
    new Thread(Done).Start();
    new Thread(Done).Start();
    Console.ReadLine();
}

static void Done(){
    lock (_lock){
        if (!_isDone){
            Console.WriteLine("Done"); // 猜猜这里面会被执行几次?
            _isDone = true;
        }
    }
}

  再大家抬高锁之后,被锁住的代码在同一个时光内只同意一个线程访问,其余的线程会被堵塞,只有等到这么些锁被假释之后另外的线程才能履行被锁住的代码。

Post Author: admin

发表评论

电子邮件地址不会被公开。 必填项已用*标注