异步线程Task

异步Async

并行,一般指并行计算,是说同一时刻有多条指令同时被执行,这些指令可能执行于同一CPU的多核上,或者多个CPU上,或者多个物理主机甚至多个网络中。

多线程,一般指同一进程中多个线程(包含其数据结构、上下文与代码片段)协作运行。在多核计算机中多个线程将有机会同时运行于多个核上,如果线程中进行的是计算,则行成并行计算。

异步,与同步相对应,是指呼叫另一操作后,不等待其结果,继续执行之后的操作,若之后没有其他操作,当前线程将进入睡眠状态,而CPU时间将有机会切至其他线程。在异步操作完成后通过回调函数的方式获取通知与结果。异步的实现方式有多种,如多线程与完成端口。多线程将异步操作放入另一线程中运行,通过轮询或回调方法得到完成通知;完成端口,由操作系统接管异步操作的调度,通过硬件中断,在完成时触发回调方法,此方式不需要占用额外线程。 阻塞

异步特点:

1异步不会造成阻塞

2异步可以启用额外的线程去执行任务。

3异步并不会缩短任务的时间

Stopwatch时间统计类

  Stopwatch watch = new Stopwatch();
  watch.Start();
  watch.Stop();
  watch.ElapsedMilliseconds//执行事件
  

异步方法调用

Console.WriteLine("****************异步调用开始**********************");
    for (int i = 0; i < 5; i++)
       {
          string name = string.Format("异步调用{0}", i);
          DoHandler method = DoSomething;
          method.BeginInvoke("异步调用", null, null);
       }
Console.WriteLine("****************异步调用结束**********************");

异步方法(含返回值)调用

   //子线程中使用返回值
    Console.WriteLine("****************异步待返回值进阶调用开始**********************");
    Func<string, long> method = DoSomethingReturn;
    //主线程中使用返回值
    IAsyncResult result = method.BeginInvoke("异步带返回值调用", t =>
    {
       long iresult = method.EndInvoke(t);
       Console.WriteLine("这里是异步返回值,当前线程={0},计算结果是{1}", Thread.CurrentThread.ManagedThreadId, iresult);
            
    }, null);

异步回调函数

  Console.WriteLine("****************异步进阶调用开始**********************");
            for (int i = 0; i < 5; i++)
            {
                string name = string.Format("异步调用{0}", i);
                DoHandler method = DoSomething;
                AsyncCallback callback = t => Console.WriteLine("当前完成的线程是ID={0},状态参数:{1}", Thread.CurrentThread.ManagedThreadId, t.AsyncState);
                //关注参数
                //参数2:回调函数
                //参数3:回调获得
                IAsyncResult result = method.BeginInvoke(name, callback, "Admin");
 
            }
 Console.WriteLine("****************异步进阶调用结束**********************");

主线程等待异步结果

方式一:

	result.AsyncWaitHandle.WaitOne(-1);//线程一直等待

方式二:

	while (!result.IsCompleted)//判断是否结束
     {
         Thread.Sleep(100);
         Console.WriteLine("请继续等待...");//类似进度条
      }

方式三:

    method.EndInvoke(result)

主线程等待就相当于程序的阻塞。

异步的优缺点:

因为异步操作无须额外的线程负担,并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少 共享变量的数量),减少了死锁的可能。当然异步操作也并非完美无暇。编写异步操作的复杂程度较高,程序主要使用回调方式进行处理,与普通人的思维方式有些 初入,而且难以调试。

适用范围:

1.在执行I/O操作时

2.网络读写 调用接口

3.数据库的操作

4.WebService

对于需要长时间CPU运算的场合,图形处理,算法执行不适合使用异步。

但是往往由于使用多线程编程的简单和符合习惯,所以很多人往往会使用线程来执行耗时较长的I/O操作,也导致很多人就误认为异步==多线程

总结:需要耗时操作的尽量都使用异步执行。

多线程

操作系统的进程和线程,线程就是围绕进程的一个子单元。

理解多线程

例 1
界面上点击某个按钮后,需要执行一个非常耗时的操作,如果不使用多线程,就只能傻等操作返回。
使用多线程,点击按钮之后,开辟1个新线程后台去执行这个耗时操作,前台界面继续执行其他菜单目录,录入数据等。

例 2
有个操作,1个线程需要20分钟完成,现在的多核cpu,可以真正同一时刻运行多个线程。
假设是双核cpu,同一时刻运行2个线程,操作就只需要10分钟可以完成。

多线程优点:

1提高应用程序执行效率【例1】

2提高CPU利用率【例2】

线程的数量

理想的线程数量<=CPU核心数量

Thread类

Thread thread = new Thread(SayHi);
thread.Start();

线程的挂起

Thread.Sleep(1000);

线程的阻塞

Thread.Join();

示例:

  for (var i = 0; i < 5; i++)
            {
                Thread th1 = new Thread(DoSomething);
                th1.Start();
                th1.Join();
            }

阻塞后,各线程会依次执行。

线程终止,不推荐使用,不安全不可控。

Thread.Abort();

前台线程、后台线程

Thread thread = new Thread(() =>
{
    // 默认为 False
    Console.WriteLine(Thread.CurrentThread.IsBackground);
});
// 可以设置为后台线程
//thread.IsBackground = true;
thread.Start();
thread.Join();

[复制代码](javascript:void(0)😉

.Net的公用语言运行时(Common Language Runtime,CLR)能区分两种不同类型的线程:前台线程和后台线程。这两者的区别就是:应用程序必须运行完所有的前台线程才可以退出;而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束。

一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序,或是定时对某些系统资源进行扫描的程序。

重要性

(1)有前台,不关闭
如果线程为前台线程,可能导致UI线程已关闭,但实际还有前台线程暗地里运行,所以程序并没有真正关闭。
(2)无前台,全关闭
当然也要注意,如果所有前台线程都关闭,后台线程会自动关闭,后台线程的代码逻辑可能没执行完就终止了。

线程传递参数

1使用ParameterizedThreadStart委托

注意:此处只能接受object类型参数,因为Thread中只接受ThreadStart委托,而该委托是一个无参无返回值的。所以此处使用ParameterizedThreadStart。

     ParameterizedThreadStart pts1 = DoSomething;
     Thread thread = new Thread(pts1);
     object name = "线程参数";
     thread.Start(name);


在线程执行过程中,该参数需要使用需要这是一个只读属性的对象

   static readonly object obj = new object();
   public static void DoSomething(string name)
        {
            Console.WriteLine("线程{0}启动", Thread.CurrentThread.ManagedThreadId);
            Stopwatch sw = new Stopwatch();
            sw.Start();
            long result = 0;
            for (int i = 0; i < 1000000000; i++)
            {
                result += i;
            }
            Thread.Sleep(2000);
            sw.Stop();
            Console.WriteLine("任务名称={3},当前线程={0},计算结果是{1},总耗时:{2}", Thread.CurrentThread.ManagedThreadId, result, sw.ElapsedMilliseconds, obj.ToString());
        }

  

2使用lambda传递参数(推荐)

 string hi = "张三";
 Thread thread = new Thread(() =>
 {
     DoSomething(hi);
 });
 thread.Start();

线程异常

注意:只要有线程抛出异常,整个程序就会停止。所以,异常必须处理。

对创建线程try catch没用,需要对线程中调用的函数添加异常处理代码

static void Main(string[] args)
{
    try // 无用的 try catch
    {
        Thread thread = new Thread(SayHi);
        thread.Start();
    }
    catch (Exception ex)
    {
        // 不会打印错误
        Console.WriteLine(ex.ToString());
    }
}
//正确
static void Main(string[] args)
{
    Thread thread = new Thread(SayHi);
    thread.Start();
    thread.Join();
}
static void SayHi()
{
    try
    {
        Console.WriteLine("hi");
        throw new Exception("error");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

线程安全

试试下列代码:

static void Main(string[] args)
{
    Thread thread = new Thread(
        () => { Check("张三"); });
    Thread thread2 = new Thread(
        () => { Check("李四"); });
    thread.Start();
    thread2.Start();
    thread.Join();
    thread2.Join();
}
static void Check(string name)
{
    User.Name = name;
    string format = @"{0} = {1}";
    for (int i = 0; i < 10; i++)
    {
        string s = string.Format(format, name, User.Name);
        Console.WriteLine(s);
        Thread.Sleep(10);
    }
}
public class User
{
    public static string Name { get; set; }
}

张三等于了李四

原因:User.Name是静态属性,也就是共享资源,多个线程访问共享资源,需要对共享资源做同步处理。

如何解决:

创建一个只读对象对对象处理使用lock(锁操作)

基本含义: lock就是把一段代码定义为临界区,所谓临界区就是同一时刻只能有一个线程来操作临界区的代码,当一个线程位于代码的临界区时,另一个线程不能进入临界区,如果 试图进入临界区,则只能一直等待(即被阻止),直到已经进入临界区的线程访问完毕,并释放锁旗标

static readonly object obj = new object();
// -------------------------
lock (obj)
{
    User.Name = name;
    string format = @"{0} = {1}";
    for (int i = 0; i < 10; i++)
    {
        string s = string.Format(format, name, User.Name);
        Console.WriteLine(s);
        Thread.Sleep(10);
    }
}

多线程的缺点

1线程占用一定资源,比如内存,CPU;创建和销毁操作都比较昂贵

2线程调度器要管理线程

3大量的创建线程,导致内存不够用,线程调度器繁忙。

4当多个线程竞争同一个静态资源时,会造成死锁。

线程池

由于线程的创建和销毁时比较昂贵的操作,所以在ASP.NET中自己设定了一个线程池,由底层进行统一调配。

线程池思想:事先创建好几个对象,需要就从池中分配,用完就返回池中

注意:

(1)线程池,只适合短时操作,不要阻塞线程池线程
(2)线程池,线程是后台线程
(3)线程池的线程数量有上限
(4)ASP.NET 使用自己的线程池

优缺点

(1)电脑的内存是有限的,每个线程,都会占用内存,如果并发数量很多,内存会爆掉。
(2)使用线程池,当并发超过一定数量,则会排队,所以,并行的请求处理时间会被延长。

用法:

ThreadPool.QueueUserWorkItem((obj) =>
 {
      DoSomething();
      Console.WriteLine(obj);

},"admin");

多线程和异步的区别

多线程是对cpu剩余劳动力的压榨,是一种技术,强调的是并发。

异步强调的是非阻塞,是一种编程模式(pattern),主要解决了UI响应被阻塞的问题,可借助线程技术或者硬件本身的计算能力解决。

背景:作为一个北漂,准备结束北漂生涯,谋划如何搬家。其余家当都变买了,就剩下有一辆小轿车,和一辆摩托车需要带回家。

  1. 阻塞式编程:先开其中一辆回去,再回来开另一辆车回去。

  2. 传统异步式编程:摩托车办理快递,我开汽车回去。注意,快递公司派件(回调)时我不一定已经开车到家,如果必须本人签收,就比较麻烦了。----此种通过回调进行异步编程的方式,没法编写符合思维顺序的代码。

  3. 基于多线程的异步编程:我获得了瞬间移动的超能力(cpu计算速度提升),以毫秒级的速度在汽车与摩托车之间切换驾驶。汽车(主线程)上有车载电话,可以使用处理其它事情。----期间频繁的上下文切换,会造成额外的损耗,造成反应能力比较差,只能开到60迈。

4.并行编程:我获得了分身的超能力(多核cpu的出现),两个我同时开两辆车回家。----充分发挥了cpu的能力,没有额外切换上下文的损耗,精力充沛,在120的时速狂飙。

Task任务

任务是什么

System.Threading.Tasks.Task,任务代表了一个异步操作。

任务非常强大,极大的简化了异步编程,并且能带来显著的性能提升。

举个简单例子,执行一个操作,我们要查询两张表的数据,我们创建两个任务分别各自查询一张表,是不是快些呢?

任务的线程是后台线程

本质上讲,任务就是一个线程池的增强版API

线程池的缺点

1线程池获取结果并不方便

2异步处理不方便

3连续的异步操作不方便

目的:

场景1:3个并发请求,通过3个接口获取数据,我们希望得到所有接口的数据进行下一步业务处理。

场景2:3个并发请求,只要其中某一个完成了,就触发一个事件结果。

任务的创建

1.构造函数创建

static void Main(string[] args)
{
    Task task = new Task(() =>
    {
        // True 线程池线程
        Console.WriteLine(Thread.CurrentThread.IsThreadPoolThread);
    });
    task.Start();
    Console.ReadLine();
}

2.Task.Factory.StartNew创建

static void Main(string[] args)
{
    Task.Factory.StartNew(() =>
    {
        // True 线程池线程
        Console.WriteLine(Thread.CurrentThread.IsThreadPoolThread);
    });
    Console.ReadLine();
}

注意:任务工厂模式下,任务会立即调用执行。

3.不使用线程池设置

static void Main(string[] args)
{
    Task task = new Task(() =>
    {
        // False 不使用线程池
        Console.WriteLine(Thread.CurrentThread.IsThreadPoolThread);
    }, TaskCreationOptions.LongRunning);
    task.Start();
    Console.ReadLine();
}

获取任务返回值

Task<string> task = new Task<string>(() =>
{
    DoSomething();
    return "此处为执行完成的结果";


});
task.Start();
Console.WriteLine(task.Result);

任务的回调

task.ContinueWith

(1)支持1个 task 接多个ContinueWith
(2)ContinueWith返回下一代 task,下一代可以继续ContinueWith

  Task<string> task = new Task<string>(() =>
    {
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
        return "boy,";
    });
    task.ContinueWith((t) =>
    {
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
        Console.WriteLine(t.Result + "come on.");
    });
    task.Start();
    Console.ReadLine();

Task的线程等待和延续

Task下的线程等待和延续主要有以下几类:

Wait

针对单个Task的实例,可以让某一个线程进入等待。

WaitAny

执行的所有线程中只要有一个线程结束,主线程就就执行,否则就卡主

    Stopwatch sw = new Stopwatch();
            sw.Start();
            Console.WriteLine("开始了,当前主线程是:{0}", Thread.CurrentThread.ManagedThreadId);
            List<Task> taskList = new List<Task>();
            Task t1 = new Task(() =>
            {
                Console.WriteLine("我是第一个任务");
                Console.WriteLine("1号开始了,当前1号线程是:{0}", Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(3000);
                Console.WriteLine("1号任务执行完成");
               
            });

            taskList.Add(t1);
            Task t2 = new Task(() =>
            {
                Console.WriteLine("我是第二个任务");
                Console.WriteLine("2号开始了,当前2号线程是:{0}", Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(6000);
                Console.WriteLine("2号任务执行完成");
            });
            
            taskList.Add(t2);
            Task t3 = new Task(() =>
            {
                Console.WriteLine("我是第三个任务");
                Console.WriteLine("3号开始了,当前3号线程是:{0}", Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(8000);
                Console.WriteLine("3号任务执行完成");
            });
            taskList.Add(t3);
            t1.Start();
            t2.Start();
            t3.Start();
            Task.WaitAny(taskList.ToArray());
            
            Console.WriteLine("全部执行完成了,执行时间{0},当前ID是:{1}",sw.ElapsedMilliseconds,Thread.CurrentThread.ManagedThreadId);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wSJTfDct-1606188603611)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201116161014828.png)]

WatiAll

表示任务列表中所有的任务都已经结束后,主线程恢复。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bNtZGowy-1606188603613)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201116161212151.png)]

WhenAny

表示任务列表中任何一个任务结束后,配合ContinueWith进行回调触发。

WhenAll

表示任务列表中所有任务都结束后,配合ContinueWith进行回调触发。

注意:WhenAny和WhenAll,都是开启一个新线程执行,不卡顿主线程。

TaskFactory

TaskFactory可以开启线程,当然也对应的线程的等待和延续。

ContinueWhenAny:等价于Task的WhenAny+ContinueWith

ContinueWhenAll:等价于Task的WhenAll+ContinueWith

1.ContinueWhenAny

Task.Factory.ContinueWhenAny(taskList.ToArray(),m=>{
	Console.WriteLine("")
})

2.ContinueWhenAll

Task.Factory.ContinueWhenAll(taskList.ToArray(),m=>{
	Console.WriteLine("")
})

异步编程Async/Await

async/await是C#5.0引入的一个关键词,其目的是让异步编程边的更加简洁,也是为了顺应异步编程的大趋势。

功能:

1使用async/await能简单的创建异步方法,异步方法可以防止耗时操作阻塞当前线程。

2使用async/await来构建异步方法,更加简洁。

语法

① 方法使用async作为修饰符

② 方法内部包含一个或者多个await表达式,表示可以异步完成的任务

③ 必须具备以下三种返回类型 void 、Task 、Task ,其中后两种的返回对象标识未来完成的工作,调用方法和异步方法可以继续执行。

④异步方法的参数可以任意类型,但是不能为out和ref参数

⑤约定俗成,一般异步方法都是以 Async作为后缀的。

⑥ 除了方法之外,Lambda表达式和匿名函数也可以作为异步对象。

异步方法的控制流

1首先第一个await之前的部分,这部分应该是少量且无需长时间等待的代码。

2await表达式,表示需要被异步执行的任务,await可以有多个。

3在await表达式之后出现的方法的其余代码。

例子1:

public static async Task DoSomethingAsync()
        {
            Console.WriteLine("异步方法同步部分1");
            long sum = 0;
            sum = await Task.Run(() =>
            {
                Console.WriteLine("异步执行部分1,线程ID:{0}",Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(2000);
                long result = 0;
                for (var i = 0; i < 1000000000; i++)
                {
                    result += i;
                }
                Console.WriteLine("异步执行部分结束");
                return result;
            });
            Console.WriteLine("计算结果{0}", sum);

        }

例子2:

 public static async void DoSomethingAsync()
        {
            Task t1 = new Task(()=> {

                Console.WriteLine("异步开始了,线程:{0}", Thread.CurrentThread.ManagedThreadId);
                long result = 0;
                for (int i = 0; i < 1000000000; i++)
                {
                    result += i;
                }
                Console.WriteLine("异步结束,计算结果{0},线程{1}", result, Thread.CurrentThread.ManagedThreadId);

            });
            t1.Start();
            await t1;
           

        }

async/await和普通异步的对比

思考:实现一个程序,每次任务休眠5秒后,返回100,打印100

 public static void DoSomething()
        {
            Console.WriteLine("异步方法开始1");
            var aaa= await Task.Run(() =>
            {
                Thread.Sleep(5000);
                return 100;
            });
            Console.WriteLine("计算结果{0}",  aaa.result);
        }

调用:

 static void Main(string[] args)
 {
    DoSomething();
    DoSomething();
    DoSomething();
 }


解释:上述代码运行后,是同步执行的,虽然Task会异步产生一个线程运行,但是由于Task中的返回值需要使用aaa.result获取,阻塞了线程,所以运行结果是同步的。

将上述代码改装为异步:

 public  static void DoSomething111()
        {
            Console.WriteLine("异步方法开始1");
            //通过第一个异步方法得到结果

           var aaa= Task.Run(() =>
            {
                Thread.Sleep(5000);
                return 100;
            });
            aaa.ContinueWith(x =>
            {
                Console.WriteLine(x.Result);
            });
        }

解释:由于Task.Run()返回的是一个Task对象,所以如果不希望阻塞线程,那么可以调用ContinueWith进行回调处理,在回调中获取Task的返回值,ContinueWith不会阻塞主线程。

优雅的写法async/await

 public async  static void DoSomething111()
        {
            Console.WriteLine("异步方法开始1");
            //通过第一个异步方法得到结果

           var aaa= await Task.Run(() =>
            {
                Thread.Sleep(5000);
                return 100;
            });

            Console.WriteLine("结果是{0}", aaa);
        }

解释:Task.Run()执行的是一个耗时任务,await标记表示等待任务结果,将结果赋值给aaa,没错,就是这么优雅。

Socket

概念

socket的英文原义是“孔”或“插座”。作为进程通信机制,取后一种意思。通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄(其实就是两个程序通信用的)。
socket非常类似于电话插座。以一个电话网为例:电话的通话双方相当于相互通信的2个程序,电话号码就是ip地址。任何用户在通话之前,首先要占有一部电话机,相当于申请一个socket;同时要知道对方的号码,相当于对方有一个固定的socket。然后向对方拨号呼叫,相当于发出连接请求。对方假如在场并空闲,拿起电话话筒,双方就可以正式通话,相当于连接成功。双方通话的过程,是一方向电话机发出信号和对方从电话机接收信号的过程,相当于向socket发送数据和从socket接收数据。通话结束后,一方挂起电话机相当于关闭socket,撤销连接。

Socket使用步骤

img

服务器端:

第一步:创建一个用于监听连接的Socket对像;

第二步:用指定的端口号和服务器的ip建立一个EndPoint对像;

第三步:用socket对像的Bind()方法绑定EndPoint;

第四步:用socket对像的Listen()方法开始监听;

第五步:接收到客户端的连接,用socket对像的Accept()方法创建一个新的用于和客户端进行通信的socket对像;

第六步:通信结束后一定记得关闭socket;

客户端:

第一步:建立一个Socket对像;

第二步:用指定的端口号和服务器的ip建立一个EndPoint对像;

第三步:用socket对像的Connect()方法以上面建立的EndPoint对像做为参数,向服务器发出连接请求;

第四步:如果连接成功,就用socket对像的Send()方法向服务器发送信息;

第五步:用socket对像的Receive()方法接受服务器发来的信息 ;

第六步:通信结束后一定记得关闭socket;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值