初步谈谈 C# 多线程、异步编程与并发服务器

多线程与异步编程可以达到避免调用线程异步阻塞作用,但是两者还是有点不同。

多线程与异步编程的异同

1.线程是cpu 调度资源和分配的基本单位,本质上是进程中的一段并发执行的代码。

2.线程编程的思维符合正常人的思维习惯,线程中的处理程序依然是顺序执行,所以编程起来比较方便,但是缺点也是明显的,多线程的使用会造成多线程之间的上下文切换带来系统花销,并且共享变量之间也是会造成死锁的问题。

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

适用范围

  在了解了线程与异步操作各自的优缺点之后,我们可以来探讨一下线程和异步的合理用途。我认为:当需要执行I/O操作时,使用异步操作比使用线程+同步 I/O操作更合适。I/O操作不仅包括了直接的文件、网络的读写,还包括数据库操作、Web Service、HttpRequest以及.net Remoting等跨进程的调用。

  而线程的适用范围则是那种需要长时间CPU运算的场合,例如耗时较长的图形处理和算法执行。但是往往由于使用线程编程的简单和符合习惯,所以很多朋友往往会使用线程来执行耗时较长的I/O操作。这样在只有少数几个并发操作的时候还无伤大雅,如果需要处理大量的并发操作时就不合适了。

二、多线程编程

刚好这段时间在看网络编程,在这里就结合多线程和网络编程,实现能够应对多客户端请求的服务端。Socket 类的 Accept() 方法一直等待,直到有客户端连接请求。Socket 网络编程可以参考C# 网络编程之 Socket 编程 。

C# 中有专门的异步网络编程方法,具体可以参考 几种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 = "hello client";
byte[] msg = System.Text.Encoding.ASCII.GetBytes(data); //将 string 转化为 byte 数组
client.Send(msg); //向客户端发生数据
Console.WriteLine("发生数据:" + data);
client.Close();
}
}

三、异步编程

基于异步的编程方法有三种:

  • 异步编程模式(APM),其中异步操作要求 Begin 和 End 方法(例如,异步写操作的 BeginWrite 和 EndWrite)。对于新的开发工作不再建议采用此模式。
  • 基于事件的异步模式 (EAP) 需要一个具有 Async 后缀的方法,还需要一个或多个事件、事件处理程序、委托类型和 EventArg 派生的类型,在 .NET Framework 2.0 版中引入的。对于新的开发工作不再建议采用此模式。
  • 基于任务的异步模式 (TAP),该模式使用一个方法表示异步操作的启动和完成。.NET Framework 4 中引入了 TAP,并且是 .NET Framework 中异步编程的建议方法。

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 = "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 = "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(); //一个没有返回值的方法
});
}

时间: 2025-01-02 15:55:08

初步谈谈 C# 多线程、异步编程与并发服务器的相关文章

多线程异步编程示例和实践-Thread和ThreadPool

说到多线程异步编程,总会说起Thread.ThreadPool.Task.TPL这一系列的技术.总结整理了一版编程示例和实践,分享给大家. 先从Thread和ThreadPool说起: 1. 创建并启动线程 2. 暂停线程 当前线程在执行Thread.Sleep方法时,会等待指定的时间(1000ms)此时,当前线程处于阻塞状态:WaitSleepJoin 3. 线程等待 当程序运行时,启动了一个耗时较长的线程打印数字,每次打印输出前需要等待1000ms,我们在主程序中调用ThreadJoin方法

C#——await与async实现多线程异步编程

曾经,我们也许用过Thread.在主线程运行的时候.新开还有一个新线程,来运行新方法. 今天看别人发给我的一段代码的时候发现了一个不认识的await,可是又感觉非常熟悉的样子,感觉是线程那块儿的东西,查了下,发现一个简单的方法实现多线程异步编程. (PS:framework 在4.5以上才干够哦~) /// <summary> /// 測试方法 /// </summary> /// <remarks>创建者:刘慧超; 创建时间:2015-08-24 20:22:14&l

Linux网络编程——tcp并发服务器(多线程)

tcp多线程并发服务器 多线程服务器是对多进程服务器的改进,由于多进程服务器在创建进程时要消耗较大的系统资源,所以用线程来取代进程,这样服务处理程序可以较快的创建.据统计,创建线程与创建进程要快 10100 倍,所以又把线程称为"轻量级"进程.线程与进程不同的是:一个进程内的所有线程共享相同的全局内存.全局变量等信息,这种机制又带来了同步问题. tcp多线程并发服务器框架: 我们在使用多线程并发服务器时,直接使用以上框架,我们仅仅修改client_fun()里面的内容. 代码示例: #

[.net 多线程]异步编程模式

从.NET 4.5开始,支持的三种异步编程模式: 基于事件的异步编程设计模式 (EAP,Event-based Asynchronous Pattern) 异步编程模型(APM,Asynchronous Programming Model) 基于任务的编程模型(TAP,Task-based Asynchronous Pattern) 基于任务的异步模式 (TAP) 是基于 System.Threading.Tasks 命名空间的 Task 和 Task<TResult>,用于表示任意异步操作.

Linux网络编程——tcp并发服务器(多进程)

一.tcp并发服务器概述 一个好的服务器,一般都是并发服务器(同一时刻可以响应多个客户端的请求).并发服务器设计技术一般有:多进程服务器.多线程服务器.I/O复用服务器等. 二.多进程并发服务器 在 Linux 环境下多进程的应用很多,其中最主要的就是网络/客户服务器.多进程服务器是当客户有请求时,服务器用一个子进程来处理客户请求.父进程继续等待其它客户的请求.这种方法的优点是当客户有请求时,服务器能及时处理客户,特别是在客户服务器交互系统中.对于一个 TCP 服务器,客户与服务器的连接可能并不

Linux网络编程——tcp并发服务器(poll实现)

想详细彻底地了解poll或看懂下面的代码请参考<Linux网络编程--I/O复用之poll函数> 代码: #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/select.h> #include <sys/time.h> #include <sys/socket.h> #incl

多线程和异步编程示例和实践-踩过的坑

上两篇文章,主要介绍了Thread.ThreadPool和TPL 多线程异步编程示例和实践-Thread和ThreadPool 多线程异步编程示例和实践-Task 本文中,分享两则我们在做多线程和异步编程中实际踩过的坑,实际生产环境遇到的问题,以及解决办法. 1. HttpClient 业务场景:使用HttpClient实现第三方业务推送,当第三方的Http服务器不通.或者返回很慢时 线程数暴涨 Asp.Net\Asp.Net MVC场景下,并发多线程导致的线程阻塞:HttpClient.Pos

C#复习笔记(5)--C#5:简化的异步编程(异步编程的基础知识)

异步编程的基础知识 C#5推出的async和await关键字使异步编程从表面上来说变得简单了许多,我们只需要了解不多的知识就可以编写出有效的异步代码. 在介绍async和await之前,先介绍一些基础的概念: 并发:同时做很多事情. 这个解释直接表明了并发的作用.终端用户程序利用并发功能,在输入数据库的同时响应用户输入.服务器应用利用并发,在处理第一个请求的同时响应第二个请求.只要你希望程序同时做多件事情,你就需要并发.几乎每个软件程序 都会受益于并发. 多线程:并发的一种形式,它采用多个线程来

异步编程async/await

什么是异步? 在异步程序中,程序代码不需要按照编写时的顺序严格执行,有时需要一在一个新的线程中运行一部分代码,有时无需创建新的 线程,但是为了更好的利用单个线程的能力,需要改变代码的执行顺序. 进程 启动程序时,系统会在内存中创建一个新的进程.进程是构成运行程序的资源的集合,这些资源包括虚地址空间.文件句柄和许多其他程序运行所需的东西. 线程 在进程内部,系统创建一个称为线程的内核对象,它代表了真正执行的程序.线程是执行线程的简称.一旦线程建立,系统会在Main方法的第一句开始线程的执行. 线程