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

多线程与异步编程的异同

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

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

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

    说到多线程异步编程,总会说起Thread.ThreadPool.Task.TPL这一系列的技术.总结整理了一版编程示例和实践,分享给大家. 先从Thread和ThreadPool说起: 1. 创建并启 ...

  2. 多线程异步编程示例和实践-Task

    上篇博文中,我们介绍了Thread和ThreadPool: 多线程异步编程示例和实践-Thread和ThreadPool 本文中我们继续,说一下TPL(Task Parallel Library, 简 ...

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

    曾经,我们也许用过Thread.在主线程运行的时候.新开还有一个新线程,来运行新方法. 今天看别人发给我的一段代码的时候发现了一个不认识的await,可是又感觉非常熟悉的样子,感觉是线程那块儿的东西, ...

  4. 谈谈c#中异步编程模型的变迁

    大家在编程过程中都会用到一些异步编程的情况.在c#的BCL中,很多api都提供了异步方法,初学者可能对各种不同异步方法的使用感到迷惑,本文主要为大家梳理一下异步方法的变迁以及如何使用异步方法. Beg ...

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

    .NET中的异步编程 - EAP/APM 从.NET 4.5开始,支持的三种异步编程模式: 基于事件的异步编程设计模式 (EAP,Event-based Asynchronous Pattern) 异 ...

  6. socket编程和并发服务器

    socket这个词可以表示很多概念: 在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP地址+端口号”就称为socket. 在TCP协议中,建立连接的两个进程 ...

  7. .Net 多线程 异步编程 Await、Async和Task

    await和async简介   await和async是在C#5中引入,并且在.NetFramewor4.5以及.NetCore中进行了支持.主要是解决性能瓶颈,并且增强系统的响应能力. msdn关于 ...

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

    想详细彻底地了解poll或看懂下面的代码请参考<Linux网络编程——I/O复用之poll函数> 代码: #include <string.h> #include <st ...

  9. C# 并发编程 (异步编程与多线程)

    并发:同时做多件事情 多线程:并发的一种形式,它采用多个线程来执行程序. 并行处理:把正在执行的大量的任务分割成小块,分配给多个同时运行的线程.并行处理是多线程的一种,而多线程是并发的一种. 异步编程 ...

随机推荐

  1. 流式计算与计算抽象化------《Designing Data-Intensive Applications》读书笔记15

    上篇的内容,我们探讨了分布式计算中的MapReduce与批处理.所以本篇我们将继续探索分布式计算优化的相关细节,并且分析MapReduce与批处理的局限性,看看流式计算是否能给我们在分布式计算层面提供 ...

  2. qt 使用msvc编译器出现乱码如何解决?字符串中存在空格?

    开发环境: 1.win7 64位 2.qt版本 windows-x86-msvc2015-5.9.0 如何解决? 1.设置qt文件编码 设置 默认UTF-8 如果编码是 UTF-8 则添加. 2.使用 ...

  3. UVA - 11270 轮廓线DP

    其实这题还能用状压DP解决,可是时间达到2000ms只能过掉POJ2411.状压DP解法详见状压DP解POJ2411 贴上POJ2411AC代码 : 2000ms 时间复杂度h*w*(2^w)*(2^ ...

  4. nyoj358 取石子(五) 斐波那契博弈

    我写代码找的规律:如果这个n是斐波那契数,那么它是P态,如2,3,5,8..... 找规律的代码: #include <cstdio> #include <cmath> #in ...

  5. 常用的CSS框架

    常用的CSS框架 之前在写自己的个人网站的时候,由于自己Web前端不是特别好,于是就去找相关的CSS框架来搭建页面了. 找到以下这么一篇文章(列出了很多常用的CSS框架): http://w3scho ...

  6. 吾八哥学Selenium(二):操作输入框/按钮的方法

    一个web页面一定少不了输入框或者按钮这两种元素,那么在Python里如何使用Selenium操作web页面里的输入框和按钮呢?本文带你简单入门. 本文采用了一个例子,就是利用Selenium打开百度 ...

  7. C语言老司机学Python (四)

    字符串格式化: 可以使用类似c语言中sprintf函数的方法进行格式化,但是函数名称是print() 如:print('常量 PI 的值近似为:%5.3f.'  %  var_PI) 注意var_PI ...

  8. SQL语句学习

    看似简单,但其实包含很多技巧思维 1.查询课程表中所有科目大于80的学生 select distinct name from student where name not in (select nam ...

  9. 一文解决python模块导入

    python 模块导入 原理 查找是按照 sys.path 中的路径挨个扫描.若都不存在则提示error. sys.path路径第一个是当前运行脚本所在的目录,其后是PYTHONPATH(一般若步专门 ...

  10. DAVINCI DM6446 开发攻略——V4L2视频驱动和应用分析

     针对DAVINCI DM6446平台,网络上也有很多网友写了V4L2的驱动,但只是解析Montavista linux-2.6.10 V4L2的原理.结构和函数,深度不够.本文决定把Montavis ...