分类: C#/.net
2016-12-23 16:58:13
一、多线程和异步的区别
多线程和异步操作两者都可以达到避免调用线程阻塞的目的。但是,多线程和异步操作还是有一些区别的。而这些区别造成了使用多线程和异步操作的时机的区别。
线程本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度;
异步本质上是计算机硬件功能,其操作无须消耗CPU时间的I/O操作;
编写异步操作的复杂程度较高,程序主要使用回调方式进行处理,处理函数可以不必使用共享变量;
多线程是顺序执行,编程简单。但是线程的使用(滥用)会给系统带来上下文切换的额外负担。并且线程间的共享变量可能造成死锁的出现。
当需要执行I/O操作时,使用异步操作比使用线程+同步 I/O操作更合适。I/O操作不仅包括了直接的文件、网络的读写,还包括数据库操作、Web Service、HttpRequest以及.net Remoting 等跨进程的调用。
线程适用那种需要长时间 CPU 运算的场合,例如耗时较长的图形处理和算法执行。
更多关于多线程和异步的异同可以参考 。
二、多线程编程
刚好这段时间在看网络编程,在这里就结合多线程和网络编程,实现能够应对多客户端请求的服务端。Socket 类的 Accept() 方法一直等待,直到有客户端连接请求。Socket 网络编程可以参考
static Socket client; static void Main(string[] args) { IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9060); //创建 IPEndPoint 对象,表示接受任何端口 9050 的客户机地址 Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // TCP server.Bind(ipep); //绑定 server.Listen(20); //监听 Console.WriteLine("正在监听..."); //下面这段代码阻塞,可以用新线程执行,但可能会出问题 while (true) { client = server.Accept(); //收到客户端请求 //开新线程发送数据 Thread recvthread = new Thread(sendData); recvthread.IsBackground = true; //后台线程 // 启动消息服务线程 recvthread.Start(); ///也可以开其他线程,如接收数据线程 } } static private void sendData() { if (client != null) { Console.WriteLine("客户机" + client.RemoteEndPoint + "连接到服务器"); string data = "http://blog.csdn.net/xgf415/article/details/hello client"; byte[] msg = System.Text.Encoding.ASCII.GetBytes(data); //将 string 转化为 byte 数组 client.Send(msg); //向客户端发生数据 Console.WriteLine("发生数据:" + data); client.Close(); } }
三、异步编程
基于异步的编程方法有三种:
Task 异步,有以下三种方法创建 Task:
Task.Factory.StartNew,比较常用。
Task.Run,是.net 4.5中增加的。
Task.FromResult,如果结果是已计算,就可以使用这种方法来创建任务。
下面就以 Task.Factory.StartNew 进行异步编程
static Socket client; static void Main(string[] args) { IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9060); //创建 IPEndPoint 对象,表示接受任何端口 9050 的客户机地址 Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // TCP server.Bind(ipep); //绑定 server.Listen(20); //监听 Console.WriteLine("正在监听..."); //下面这段代码阻塞,可以放到子线程执行,但是可能会出现问题,可以看最后面分析 while (true) { client = server.Accept(); //创建并启动 task Task.Factory.StartNew(() => { sendData(); //一个没有返回值的方法 }); } } static private void sendData() { if (client != null) { Console.WriteLine("客户机" + client.RemoteEndPoint + "连接到服务器"); string data = "http://blog.csdn.net/xgf415/article/details/hello client"; byte[] msg = System.Text.Encoding.ASCII.GetBytes(data); //将 string 转化为 byte 数组 client.Send(msg); //向客户端发生数据 Console.WriteLine("发生数据:" + data); client.Close(); } }
使用 Async 与 Await 进行异步编程
static Socket client; static Socket server; static void Main(string[] args) { IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9060); //创建 IPEndPoint 对象,表示接受任何端口 9050 的客户机地址 server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // TCP server.Bind(ipep); //绑定 server.Listen(20); //监听 Console.WriteLine("正在监听..."); accept(); } static async void accept() { await acceptAsync(); } static async Task acceptAsync() //异步接受请求 { while (true) { client = server.Accept(); //收到客户端请求 await sendData(); } } static async Task sendData() //异步发生数据 { if (client != null) { Console.WriteLine("客户机" + client.RemoteEndPoint + "连接到服务器"); string data = "http://blog.csdn.net/xgf415/article/details/hello client"; byte[] msg = System.Text.Encoding.ASCII.GetBytes(data); //将 string 转化为 byte 数组 client.Send(msg); //向客户端发生数据 //添加一个异步方法 await Task.Delay(1000); Console.WriteLine("发生数据:" + data); client.Close(); } }
由于 Main() 函数不能设置为 async 模式,所以增加了一个accept 函数,使用 await 来执行异步操作 acceptAsync() ,等待接受客户端的请求。同时在异步操作 acceptAsync() 中执行异步 sendData() ,异步发送数据。
一个问题:在上面的三个程序中,采用多线程和Task.Factory.StartNew 实现服务端的两个程序,如果把下面两段代码作为子线程或者异步函数执行,本来是阻塞的函数 client = server.Accept(),却没有等待客户端连接,直接执行过去了??是因为 accept()在同时在静态函数和多线程中的关系????(因为在非静态函数中这样并没有问题),但是使用Async 与 Await 执行的异步函数却能正常执行。
while (true) { client = server.Accept(); //收到客户端请求 //开新线程发送数据 Thread recvthread = new Thread(sendData); recvthread.IsBackground = true; //后台线程 // 启动消息服务线程 recvthread.Start(); ///也可以开其他线程,如接收数据线程 }
while (true) { client = server.Accept(); //创建并启动 task Task.Factory.StartNew(() => { sendData(); //一个没有返回值的方法 }); }