推荐 最新
全能人才

如何搭建具有DDNS功能的本地DNS服务器?

具有现成的,具有ddns功能的dns服务器程序 使用了dynv6,发现挺好用的,想着,能不能自己在局域网搭建一个具有类似的,具有ddns功能的dns服务器

8
1
0
浏览量326
超超

硬核图解!断网了,还能ping通 127.0.0.1 吗?为什么?

你女神爱不爱你,你问她,她可能不会告诉你。但网通不通,你 ping 一下就知道了。可能看到标题,你就知道答案了,但是你了解背后的原因吗?那如果把 127.0.0.1 换成 0.0.0.0 或 localhost 会怎么样呢? 你知道这几个IP有什么区别吗?以前面试的时候就遇到过这个问题,大家看个动图了解下面试官和我当时的场景,求当时小白的心里阴影面积。话不多说,我们直接开车。拔掉网线,断网。然后在控制台输入 ping 127.0.0.1。$ ping 127.0.0.1 PING 127.0.0.1 (127.0.0.1): 56 data bytes 64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.080 ms 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.093 ms 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.074 ms 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.079 ms 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.079 ms ^C --- 127.0.0.1 ping statistics --- 5 packets transmitted, 5 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 0.074/0.081/0.093/0.006 ms说明,拔了网线,ping 127.0.0.1 是能ping通的。其实这篇文章看到这里,标题前半个问题已经被回答了。但是我们可以再想深一点。为什么断网了还能 ping 通 127.0.0.1 呢?这能说明你不用交网费就能上网吗?不能。首先我们需要进入基础科普环节。不懂的同学看了就懂了,懂的看了就当查漏补缺吧。什么是127.0.0.1首先,这是个 IPV4 地址。IPV4 地址有 32 位,一个字节有 8 位,共 4 个字节。其中127 开头的都属于回环地址,也是 IPV4 的特殊地址,没什么道理,就是人为规定的。而127.0.0.1是众多回环地址中的一个。之所以不是 127.0.0.2 ,而是 127.0.0.1,是因为源码里就是这么定义的,也没什么道理。/* Address to loopback in software to local host. */ #define INADDR_LOOPBACK 0x7f000001 /* 127.0.0.1 */IPv4 的地址是 32 位的,2的32次方,大概是40+亿。地球光人口就76亿了,40亿IP这点量,塞牙缝都不够,实际上IP也确实用完了。所以就有了IPV6, IPv6 的地址是 128 位的,大概是2的128次方≈10的38次方。据说地球的沙子数量大概是 10的23次方,所以IPV6的IP可以认为用不完。IPV4以8位一组,每组之间用 . 号隔开。IPV6就以16位为一组,每组之间用 : 号隔开。如果全是0,那么可以省略不写。在IPV4下的回环地址是 127.0.0.1,在IPV6下,表达为 ::1 。中间把连续的0给省略了,之所以不是7个 冒号,而是2个冒号: , 是因为一个 IPV6 地址中只允许出现⼀次两个连续的冒号。多说一句: 在IPV4下用的是 ping 127.0.0.1 命令。 在IPV6下用的是 ping6 ::1 命令。什么是 pingping 是应用层命令,可以理解为它跟游戏或者聊天软件属于同一层。只不过聊天软件可以收发消息,还能点个赞什么的,有很多复杂的功能。而 ping 作为一个小软件,它的功能比较简单,就是尝试发送一个小小的消息到目标机器上,判断目的机器是否可达,其实也就是判断目标机器网络是否能连通。ping应用的底层,用的是网络层的ICMP协议。虽然ICMP协议和IP协议都属于网络层协议,但其实ICMP也是利用了IP协议进行消息的传输。所以,大家在这里完全可以简单的理解为 ping 某个IP 就是往某个IP地址发个消息。TCP发数据和ping的区别一般情况下,我们会使用 TCP 进行网络数据传输,那么我们可以看下它和 ping 的区别。ping和其他应用层软件都属于应用层。那么我们横向对比一下,比方说聊天软件,如果用的是TCP的方式去发送消息。为了发送消息,那就得先知道往哪发。linux里万物皆文件,那你要发消息的目的地,也是个文件,这里就引出了socket 的概念。要使用 socket , 那么首先需要创建它。在 TCP 传输中创建的方式是 socket(AF_INET, SOCK_STREAM, 0);,其中 AF_INET 表示将使用 IPV4 里 host:port 的方式去解析待会你输入的网络地址。SOCK_STREAM 是指使用面向字节流的 TCP 协议,工作在传输层。创建好了 socket 之后,就可以愉快的把要传输的数据写到这个文件里。调用 socket 的sendto接口的过程中进程会从用户态进入到内核态,最后会调用到 sock_sendmsg 方法。然后进入传输层,带上TCP头。网络层带上IP头,数据链路层带上 MAC头等一系列操作后。进入网卡的发送队列 ring buffer ,顺着网卡就发出去了。回到 ping , 整个过程也基本跟 TCP 发数据类似,差异的地方主要在于,创建 socket 的时候用的是 socket(AF_INET,SOCK_RAW,IPPROTO_ICMP),SOCK_RAW 是原始套接字 ,工作在网络层, 所以构建ICMP(网络层协议)的数据,是再合适不过了。ping 在进入内核态后最后也是调用的 sock_sendmsg 方法,进入到网络层后加上ICMP和IP头后,数据链路层加上MAC头,也是顺着网卡发出。因此 本质上ping 跟 普通应用发消息 在程序流程上没太大差别。这也解释了**为什么当你发现怀疑网络有问题的时候,别人第一时间是问你能ping通吗?**因为可以简单理解为ping就是自己组了个数据包,让系统按着其他软件发送数据的路径往外发一遍,能通的话说明其他软件发的数据也能通。为什么断网了还能 ping 通 127.0.0.1前面提到,有网的情况下,ping 最后是通过网卡将数据发送出去的。那么断网的情况下,网卡已经不工作了,ping 回环地址却一切正常,我们可以看下这种情况下的工作原理。从应用层到传输层再到网络层。这段路径跟ping外网的时候是几乎是一样的。到了网络层,系统会根据目的IP,在路由表中获取对应的路由信息,而这其中就包含选择哪个网卡把消息发出。当发现目标IP是外网IP时,会从"真网卡"发出。当发现目标IP是回环地址时,就会选择本地网卡。本地网卡,其实就是个**"假网卡",它不像"真网卡"那样有个ring buffer什么的,"假网卡"会把数据推到一个叫 input_pkt_queue 的 链表 中。这个链表,其实是所有网卡共享的,上面挂着发给本机的各种消息。消息被发送到这个链表后,会再触发一个软中断**。专门处理软中断的工具人**"ksoftirqd"** (这是个内核线程),它在收到软中断后就会立马去链表里把消息取出,然后顺着数据链路层、网络层等层层往上传递最后给到应用程序。ping 回环地址和通过TCP等各种协议发送数据到回环地址都是走这条路径。整条路径从发到收,都没有经过"真网卡"。**之所以127.0.0.1叫本地回环地址,可以理解为,消息发出到这个地址上的话,就不会出网络,在本机打个转就又回来了。**所以断网,依然能 ping 通 127.0.0.1。ping回环地址和ping本机地址有什么区别我们在mac里执行 ifconfig 。$ ifconfig lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384 inet 127.0.0.1 netmask 0xff000000 ... en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 inet 192.168.31.6 netmask 0xffffff00 broadcast 192.168.31.255 ...能看到 lo0,表示本地回环接口,对应的地址,就是我们前面提到的 127.0.0.1 ,也就是回环地址。和 eth0,表示本机第一块网卡,对应的IP地址是192.168.31.6,管它叫本机IP。之前一直认为ping本机IP的话会通过"真网卡"出去,然后遇到第一个路由器,再发回来到本机。为了验证这个说法,可以进行抓包,但结果跟上面的说法并不相同。可以看到 ping 本机IP 跟 ping 回环地址一样,相关的网络数据,都是走的 lo0,本地回环接口,也就是前面提到的**"假网卡"**。只要走了本地回环接口,那数据都不会发送到网络中,在本机网络协议栈中兜一圈,就发回来了。因此 ping回环地址和ping本机地址没有区别。127.0.0.1 和 localhost 以及 0.0.0.0 有区别吗回到文章开头动图里的提问,算是面试八股文里的老常客了。以前第一次用 nginx 的时候,发现用这几个 IP,都能正常访问到 nginx 的欢迎网页。一度认为这几个 IP 都是一样的。    但本质上还是有些区别的。首先 localhost 就不叫 IP,它是一个域名,就跟 "baidu.com",是一个形式的东西,只不过默认会把它解析为 127.0.0.1 ,当然这可以在 /etc/hosts 文件下进行修改。所以默认情况下,使用 localhost 跟使用 127.0.0.1 确实是没区别的。其次就是 0.0.0.0,执行 ping 0.0.0.0 ,是会失败的,因为它在IPV4中表示的是无效的目标地址。$ ping 0.0.0.0 PING 0.0.0.0 (0.0.0.0): 56 data bytes ping: sendto: No route to host ping: sendto: No route to host但它还是很有用处的,回想下,我们启动服务器的时候,一般会 listen 一个 IP 和端口,等待客户端的连接。如果此时 listen 的是本机的 0.0.0.0 , 那么它表示本机上的所有IPV4地址。/* Address to accept any incoming messages. */ #define INADDR_ANY ((unsigned long int) 0x00000000) /* 0.0.0.0 */举个例子。刚刚提到的 127.0.0.1 和 192.168.31.6 ,都是本机的IPV4地址,如果监听 0.0.0.0 ,那么用上面两个地址,都能访问到这个服务器。当然, 客户端 connect 时,不能使用 0.0.0.0 。必须指明要连接哪个服务器IP。总结127.0.0.1 是回环地址。localhost是域名,但默认等于 127.0.0.1。ping 回环地址和 ping 本机地址,是一样的,走的是lo0 "假网卡",都会经过网络层和数据链路层等逻辑,最后在快要出网卡前狠狠拐了个弯, 将数据插入到一个链表后就软中断通知 ksoftirqd 来进行收数据的逻辑,压根就不出网络。所以断网了也能 ping 通回环地址。如果服务器 listen 的是 0.0.0.0,那么此时用127.0.0.1和本机地址都可以访问到服务。最后最近工作上的事情太忙,本来就黑的黑眼圈,就更黑了,鸽了大家这么久实在不好意思哈。这篇文章里,有几张大图本来都是动图,但是发现动起来之后发现字太小,点开来放大之后图又不会动了。有些影响体验,我就先改成静态图吧。参考资料《127.0.0.1 之本机网络通信过程知多少 ?!》—— 推荐关注飞哥的《开发内功修炼》

0
0
0
浏览量1182
没晒干的咸鱼

C# Task.Run 和 Task.Factory.StartNew 区别

有小伙伴问我,为什么不推荐他使用 Task.Factory.StartNew ,因为 Task.Run 是比较新的方法。本文告诉大家 Task.Run 和 Task.Factory.StartNew 区别有很多博客说到了 Task.Run 和 Task.Factory.StartNew 区别,所以我也就不需要展开告诉大家。只需要知道 Task.Run 是在 dotnet framework 4.5 之后才可以使用,但是 Task.Factory.StartNew 可以使用比 Task.Run 更多的参数,可以做到更多的定制。可以认为 Task.Run 是简化的 Task.Factory.StartNew 的使用,除了需要指定一个线程是长时间占用的,否则就使用 Task.Run创建新线程下面来告诉大家使用两个函数创建新的线程 Task.Run(() => { var foo = 2; }); 这时 foo 的创建就在另一个线程,需要知道 Task.Run 用的是线程池,也就是不是调用这个函数就会一定创建一个新的线程,但是会在另一个线程运行。 Task.Factory.StartNew(() => { var foo = 2; }); 可以看到,两个方法实际上是没有差别,但是Task.Run比较好看,所以推荐使用Task.Run。等待线程创建的线程,如果需要等待线程执行完成在继续,那么可以使用 await 等待 private static async void SeenereKousa() { Console.WriteLine("开始 线程"+Thread.CurrentThread.ManagedThreadId); await Task.Run(() => { Console.WriteLine("进入 线程" + Thread.CurrentThread.ManagedThreadId); }); Console.WriteLine("退出 线程"+Thread.CurrentThread.ManagedThreadId); } 但是需要说的是这里使用 await 主要是给函数调用的外面使用,上面代码在函数里面使用 await 函数是 void 那么和把代码放在 task 里面是相同 private static async void SeenereKousa() { Console.WriteLine("开始 线程"+Thread.CurrentThread.ManagedThreadId); await Task.Run(() => { Console.WriteLine("进入 线程" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("退出 线程"+Thread.CurrentThread.ManagedThreadId); }); } 但是如果把 void 修改为 Task ,那么等待线程才有用除了使用 await 等待,还可以使用 WaitAll 等待 Console.WriteLine("开始 线程" + Thread.CurrentThread.ManagedThreadId); var t = Task.Run(() => { Console.WriteLine("进入 线程" + Thread.CurrentThread.ManagedThreadId); }); Task.WaitAll(t); Console.WriteLine("退出 线程" + Thread.CurrentThread.ManagedThreadId); 使用 WaitAll 是在调用 WaitAll 的线程等待,也就是先在线程 1 运行,然后异步到 线程2 运行,这时线程1 等待线程2运行完成再继续,所以输出开始 线程1 进入 线程2 退出 线程1 长时间运行两个函数最大的不同在于 Task.Factory.StartNew 可以设置线程是长时间运行,这时线程池就不会等待这个线程回收 Task.Factory.StartNew(() => { for (int i = 0; i < 100; i++) { var foo = 2; } Console.WriteLine("进行 线程" + Thread.CurrentThread.ManagedThreadId); }, TaskCreationOptions.LongRunning); 所以在需要设置线程是长时间运行的才需要使用 Task.Factory.StartNew 不然就使用 Task.Run调用 Task.Run(foo) 就和使用下面代码一样Task.Factory.StartNew(foo, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); 实际上 Task.Run(foo) 可以认为是对 Task.Factory.StartNew 封装,使用简单的默认的参数。如果需要自己定义很多参数,就请使用 Task.Factory.StartNew 定义参数。

0
0
0
浏览量2006
没晒干的咸鱼

.Net Core WebAPI 基于Task的同步&异步编程

await 和 async异步的实质就是线程的切换(遇到await),同一请求下,异步和同步的执行时间是一样的,但有人说异步可以提高XXX性能,但具体是什么性能呢?又说不上来,其实就只提高并发量,并不能提升你应用程序处理的速度使用异步的目的是用尽量少的线程保证相同的并发量。线程的总数少了,线程切换消耗的资源就小了,相对来提供给客户任务的资源就多了,性能就在这里。CPU 在大量线程的情况下,20%的时间片在线程切换上,客户任务有80%的资源使用。减少了线程数,只有5%的时间片消耗在线程切换,客户任务有95%的资源使用。减少不必要的线程等待,原来线程工作50%+等待闲置50%,现在把等待闲置50%省掉,就会提升1倍的性能。在方法定义返回类型的前面增加async关键字表示该方法为异步方法。而返回值必须是void 或者为Task<T>。T为返回值的类型;在调用被async修饰过的异步方法时可以使用await关键字变为同步方法,语法为调用前增加await关键字;string result = await loadString();方法名称一般是Async 结尾。可以包含一个或者多个await表达式。异步方法的参数不能使用ref和out参数。方法头包含async关键字,并且在返回类型之前。除了方法之外, Lambda 表达式和匿名函数也可以作为异步对象。async void该方式声明的方法是无法使用 catch 捕获异常的,所以以下代码的 try、catch 并没什么卵用。在 Task 上加上 ConfigureAwait(false),此设置代表当 async 中的异步任务完成后,不读取当时调用它的原线程的上下文信息,而是在线程池上下文中执行 async 方法的剩余部分。

0
0
0
浏览量2009
没晒干的咸鱼

异步编程一些示例

一些示例取消一个异步任务或一组任务(C#)。在一段时间后取消异步任务 (C#)在完成一个异步任务后取消剩余任务 (C#)启动多个异步任务并在其完成时进行处理 (C#)项目将创建一个 UI,其中包含用于启动进程和取消进程的按钮,如下图所示。 这些按钮名为 startButton 和 cancelButton。取消一个异步任务或一组任务 (C#)如果不想等待异步应用程序完成,可以设置一个按钮用来取消它。 通过遵循本主题中的示例,可以为下载一个或一组网站内容的应用程序添加一个取消按钮。取消一个任务下列代码是取消单个任务示例的完整 MainWindow.xaml.cs 文件。using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Net.Http; using System.Threading; namespace CancelATask { public partial class MainWindow : Window { //声明一个System.Threading.CancellationTokenSource. CancellationTokenSource cts; public MainWindow() { InitializeComponent(); } private async void startButton_Click(object sender, RoutedEventArgs e) { //实例化CancellationTokenSource. cts = new CancellationTokenSource(); resultsTextBox.Clear(); try { //如果请求取消,则发送令牌以携带消息。 int contentLength = await AccessTheWebAsync(cts.Token); resultsTextBox.Text += String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength); } catch (OperationCanceledException) { //如果请求取消,则返回OperationCanceledException resultsTextBox.Text += "\r\nDownload canceled.\r\n"; } catch (Exception) { resultsTextBox.Text += "\r\nDownload failed.\r\n"; } //下载完成后,将CancellationTokenSource设置为空 cts = null; } //为Cancel 按钮添加一个事件处理程序。 private void cancelButton_Click(object sender, RoutedEventArgs e) { if (cts != null) { cts.Cancel(); } } //提供一个CancellationToken参数 async Task<int> AccessTheWebAsync(CancellationToken ct) { HttpClient client = new HttpClient(); resultsTextBox.Text += String.Format("\r\nReady to download.\r\n"); //您可能需要放慢速度才能有机会取消。 await Task.Delay(2500); //GetAsync返回一个Task<HttpResponseMessage> //如果选择Cancel按钮,则ct参数将携带消息。 HttpResponseMessage response = await client.GetAsync("http://www.twitter.com/dd470362.aspx", ct); //从HttpResponseMessage中检索网站内容 byte[] urlContents = await response.Content.ReadAsByteArrayAsync(); //该方法的结果是下载网站页面的字符长度 return urlContents.Length; } } // 成功下载的输出: // Ready to download. // Length of the downloaded string: 158125. // 或者,如果取消: // Ready to download. // Download canceled. } 取消任务列表下列代码是取消任务列表示例的完整 MainWindow.xaml.cs 文件。using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Net.Http; using System.Threading; namespace CancelAListOfTasks { public partial class MainWindow : Window { //声明一个System.Threading.CancellationTokenSource. CancellationTokenSource cts; public MainWindow() { InitializeComponent(); } private async void startButton_Click(object sender, RoutedEventArgs e) { cts = new CancellationTokenSource(); resultsTextBox.Clear(); try { await AccessTheWebAsync(cts.Token); resultsTextBox.Text += "\r\nDownloads complete."; } catch (OperationCanceledException) { resultsTextBox.Text += "\r\nDownloads canceled."; } catch (Exception) { resultsTextBox.Text += "\r\nDownloads failed."; } cts = null; } //为Cancel 按钮添加一个事件处理程序。 private void cancelButton_Click(object sender, RoutedEventArgs e) { if (cts != null) { cts.Cancel(); } } //将返回类型更改为Task,因为该方法没有返回语句。 async Task AccessTheWebAsync(CancellationToken ct) { HttpClient client = new HttpClient(); //调用SetUpURLList来创建一个Web地址列表 List<string> urlList = SetUpURLList(); //添加一个循环来处理网址列表 foreach (var url in urlList) { //GetAsync返回一个Task<HttpResponseMessage> //如果选择Cancel按钮,则ct参数将携带消息。 //请注意,取消按钮可以取消所有剩余的下载 HttpResponseMessage response = await client.GetAsync(url, ct); //从HttpResponseMessage中检索网站内容 byte[] urlContents = await response.Content.ReadAsByteArrayAsync(); resultsTextBox.Text += String.Format("\r\nLength of the downloaded string: {0}.\r\n", urlContents.Length); } } private List<string> SetUpURLList() { List<string> urls = new List<string> { "http://www.twitter.com", "http://www.twitter.com/hh290138.aspx", "http://www.twitter.com/hh290140.aspx", "http://www.twitter.com/dd470362.aspx", "http://www.twitter.com/aa578028.aspx", "http://www.twitter.com/ms404677.aspx", "http://www.twitter.com/ff730837.aspx" }; return urls; } } //如果您不选择取消,则输出: //Length of the downloaded string: 35939. //Length of the downloaded string: 237682. //Length of the downloaded string: 128607. //Length of the downloaded string: 158124. //Length of the downloaded string: 204890. //Length of the downloaded string: 175488. //Length of the downloaded string: 145790. //Downloads complete. //如果您选择取消: //Length of the downloaded string: 35939. //Length of the downloaded string: 237682. //Length of the downloaded string: 128607. //Downloads canceled. } 在一段时间后取消异步任务 (C#)如果不希望等待操作结束,可使用 CancellationTokenSource.CancelAfter 方法在一段时间后取消异步操作。 此方法会计划取消未在 CancelAfter 表达式指定的时间段内完成的任何关联任务。完整的示例请注意,必须为 System.Net.Http 添加引用。using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Net.Http; using System.Threading; namespace CancelAfterTime { public partial class MainWindow : Window { CancellationTokenSource cts; public MainWindow() { InitializeComponent(); } private async void startButton_Click(object sender, RoutedEventArgs e) { cts = new CancellationTokenSource(); resultsTextBox.Clear(); try { //设置CancellationTokenSource以在2.5秒后取消。 (您可以调整时间。) cts.CancelAfter(2500); await AccessTheWebAsync(cts.Token); resultsTextBox.Text += "\r\nDownloads succeeded.\r\n"; } catch (OperationCanceledException) { resultsTextBox.Text += "\r\nDownloads canceled.\r\n"; } catch (Exception) { resultsTextBox.Text += "\r\nDownloads failed.\r\n"; } cts = null; } //您仍然可以使用取消按钮。 private void cancelButton_Click(object sender, RoutedEventArgs e) { if (cts != null) { cts.Cancel(); } } async Task AccessTheWebAsync(CancellationToken ct) { HttpClient client = new HttpClient(); List<string> urlList = SetUpURLList(); foreach (var url in urlList) { //取消按钮取消所有剩余的下载。 HttpResponseMessage response = await client.GetAsync(url, ct); //从HttpResponseMessage中检索网站内容 byte[] urlContents = await response.Content.ReadAsByteArrayAsync(); resultsTextBox.Text += String.Format("\r\nLength of the downloaded string: {0}.\r\n", urlContents.Length); } } private List<string> SetUpURLList() { List<string> urls = new List<string> { "http://www.twitter.com", "http://www.twitter.com/ee256749.aspx", ... }; return urls; } } //示例输出: // Length of the downloaded string: 35990. // Length of the downloaded string: 407399. // Length of the downloaded string: 226091. // Downloads canceled. } 在完成一个异步任务后取消剩余任务 (C#)通过结合使用 Task.WhenAny 方法和 CancellationToken,可在一个任务完成时取消所有剩余任务。WhenAny 方法采用任务集合中的一个参数。该方法启动所有任务,并返回单个任务。 当集合中任意任务完成时,完成单个任务。此示例演示如何结合使用取消标记与 WhenAny 保留任务集合中第一个要完成的任务,并取消剩余任务。 每个任务都下载网站内容。本示例显示第一个完成的下载的内容长度,并取消其他下载。完整的示例using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Net.Http; using System.Threading; namespace CancelAfterOneTask { public partial class MainWindow : Window { CancellationTokenSource cts; public MainWindow() { InitializeComponent(); } private async void startButton_Click(object sender, RoutedEventArgs e) { cts = new CancellationTokenSource(); resultsTextBox.Clear(); try { await AccessTheWebAsync(cts.Token); resultsTextBox.Text += "\r\nDownload complete."; } catch (OperationCanceledException) { resultsTextBox.Text += "\r\nDownload canceled."; } catch (Exception) { resultsTextBox.Text += "\r\nDownload failed."; } cts = null; } private void cancelButton_Click(object sender, RoutedEventArgs e) { if (cts != null) { cts.Cancel(); } } async Task AccessTheWebAsync(CancellationToken ct) { HttpClient client = new HttpClient(); List<string> urlList = SetUpURLList(); //创建一个查询,该查询在执行时返回一组任务。 IEnumerable<Task<int>> downloadTasksQuery = from url in urlList select ProcessURLAsync(url, client, ct); //使用ToArray执行查询并开始下载任务 Task<int>[] downloadTasks = downloadTasksQuery.ToArray(); //调用WhenAny然后等待结果。首先完成的任务分配给firstFinishedTask。 Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks); //取消下载的其余部分,你只是想要第一个。 cts.Cancel(); //等待第一个完成的任务并显示结果。 var length = await firstFinishedTask; resultsTextBox.Text += String.Format("\r\nLength of the downloaded website: {0}\r\n", length); } //将网站的处理步骤集中到一个异步方法中。 async Task<int> ProcessURLAsync(string url, HttpClient client, CancellationToken ct) { //GetAsync返回一个Task<HttpResponseMessage> HttpResponseMessage response = await client.GetAsync(url, ct); //从HttpResponseMessage中检索网站内容 byte[] urlContents = await response.Content.ReadAsByteArrayAsync(); return urlContents.Length; } private List<string> SetUpURLList() { List<string> urls = new List<string> { "http://www.twitter.com", "http://www.twitter.com/hh290138.aspx", "http://www.twitter.com/hh290140.aspx", ... }; return urls; } } //示例输出: // Length of the downloaded website: 158856 // Download complete. } 启动多个异步任务并在其完成时进行处理 (C#)通过使用 Task.WhenAny,可以同时启动多个任务,并在它们完成时逐个对它们进行处理,而不是按照它们的启动顺序进行处理。下面的示例使用查询来创建一组任务。 每个任务都下载指定网站的内容。 在对 while 循环的每次迭代中,对 WhenAny 的等待调用返回任务集合中首先完成下载的任务。 此任务从集合中删除并进行处理。 循环重复进行,直到集合中不包含任何任务。完整的示例请注意,必须为 System.Net.Http 添加引用。using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Net.Http; using System.Threading;

0
0
0
浏览量2010
没晒干的咸鱼

异步编程(或多线程编程)

1 .NET多线程是什么?1.1 进程与线程进程是一种正在执行的程序。线程是程序中的一个执行流。多线程是指一个程序中可以同时运行多个不同的线程来执行不同的任务。1.2 .NET中的线程Thread是创建和控制线程的类。ManagedThreadId是线程ID。CurrentThread是获取当前正在运行的线程。1.3 同步与异步同步是调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。(单线程)异步调用一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。(多线程)1.4 .NET中的多线程发展主要有Thread,ThreadPool,TaskThread就是线程,需要自己调度,直接跟系统对接,相对管理比较复杂及效率差。ThreadPool是Thread的一个升级版,ThreadPool是从线程池中获取线程,如果线程池中又空闲的元素,则直接调用,如果没有才会创建,而Thread则是会一直创建新的线程,要知道开启一个线程就算什么事都不做也会消耗大约1m的内存,是非常浪费性能的。但是ThreadPool提供的接口比较少。Task和ThreadPool是一样的,都是从线程池中取空闲的线程。比ThreadPool调用接口更加丰富。目前.Net使用多线程管理,应该优先使用Task。代码: /// <summary> /// 多线程发展历史 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnHistory_Click(object sender, EventArgs e) { Console.WriteLine($"Thread start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); var threadStart = new ThreadStart(DoNothing); var thread = new Thread(threadStart); thread.Start(); Console.WriteLine($"Thread end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); Thread.Sleep(3000); Console.WriteLine($"ThreadPool start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); var callback = new WaitCallback(DoNothing); ThreadPool.QueueUserWorkItem(callback); Console.WriteLine($"ThreadPool end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); Thread.Sleep(3000); Console.WriteLine($"Task start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); Action action = DoNothing; Task task = new Task(action); task.Start(); Console.WriteLine($"Task end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); } 2 为什么需要多线程?特点:卡界面:单线程卡,多线程不卡性能好:单线程差,多线程好(资源换性能)执行顺序:单线程顺序,多线程无序代码: /// <summary> /// 同步(单线程) /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSync_Click(object sender, EventArgs e) { Console.WriteLine($"btnSync_Click start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); for (int i = 0; i < 5; i++) { DoNothing(); } Console.WriteLine($"btnSync_Click end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); } /// <summary> /// 异步(多线程) /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnAsync_Click(object sender, EventArgs e) { Console.WriteLine($"btnAsync_Click start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); for (int i = 0; i < 5; i++) { var ts = new ThreadStart(DoNothing); var t = new Thread(ts); t.Start(); } Console.WriteLine($"btnAsync_Click end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); } private void DoNothing() { Console.WriteLine($"DoNothing start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); Thread.Sleep(2000); Console.WriteLine($"DoNothing end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); } 3 如何使用.NET多线程?3.1 Task3.1.1 创建任务1、通过调用任务类构造函数实例化,但通过调用其Start()启动任务。Task t1 = new Task(action, "alpha"); t1.Start(); 2、通过调用TaskFactory.StartNew(Action < Object>,Object)方法在单个方法调用中实例化和启动任务。 Task t2 = Task.Factory.StartNew(action, "beta"); 3、通过调用Run(Action)方法在单个方法调用中实例化和启动任务。Task t3 = Task.Run(action); 3.1.2 从任务中返回值Result 属性将阻止调用线程,直到任务完成。Task<int> task1 = Task<int>.Factory.StartNew(() => 1); int i = task1.Result; 3.1.3 等待任务完成可以通过调用 Wait 方法来等待一个或多个任务完成,从而同步调用线程的执行以及它启动的异步任务。调用无参数 Wait() 方法以无条件等待,直到任务完成。调用Wait(Int32)和 Wait(TimeSpan) 方法会阻止调用线程,直到任务完成或超时间隔(以先达到者为准)为止。调用WaitAny(Task[])方法等待一组任务中第一个任务完成。调用WaitAll(Task[])方法来等待一系列任务全部完成。3.1.4 异常处理调用代码可以通过使用 try/catch 块中的以下任意方法来处理异常:await tasktask.Wait()task.Resulttask.GetAwaiter().GetResult()代码:var task1 = Task.Run(() => { throw new Exception("This exception is expected!"); }); try { task1.Wait(); } catch (Exception e) { Console.WriteLine(e.Message); } Console.ReadKey(); 3.1.5 取消任务你可以使用 CancellationTokenSource 类在以后某一时间发出取消请求。static void Main(string[] args) { var tokenSource = new CancellationTokenSource(); var token = tokenSource.Token; var tasks = new ConcurrentBag<Task>(); Task tc; for (int i = 0; i < 10; i++) { var k = i; tc = Task.Run(() => DoNothing(k, token), token); tasks.Add(tc); } char ch = Console.ReadKey().KeyChar; if (ch == 'c' || ch == 'C') { tokenSource.Cancel(); Console.WriteLine("\n开始取消任务."); } try { Task.WhenAll(tasks.ToArray()); } catch (OperationCanceledException) { Console.WriteLine($"\n{nameof(OperationCanceledException)} thrown\n"); } finally { tokenSource.Dispose(); } Console.ReadKey(); } private static void DoNothing(int i, CancellationToken ct) { Console.WriteLine($"DoNothing start index:{i} id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); Thread.Sleep(i * 1000); if (ct.IsCancellationRequested) { Console.WriteLine($"任务已取消 index:{i} id:{Thread.CurrentThread.ManagedThreadId} "); ct.ThrowIfCancellationRequested(); } Console.WriteLine($"DoNothing end index:{i} id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); } 3.1.6 实际案例代码:/// <summary> /// Task实际案例 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnTask_Click(object sender, EventArgs e) { Console.WriteLine($"项目组接到任务 id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); Console.WriteLine($"项目经理设计数据库,设计原型,分配任务 id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); List<Task> tasks = new List<Task>(); tasks.Add(Task.Run(() => Coding("赵XX","前端页面"))); tasks.Add(Task.Run(() => Coding("王XX", "IOS页面"))); tasks.Add(Task.Run(() => Coding("黄XX", "后端接口"))); tasks.Add(Task.Run(() => Coding("杜XX", "后端接口"))); TaskFactory taskFactory = new TaskFactory(); taskFactory.ContinueWhenAll(tasks.ToArray(), t => { Console.WriteLine($"项目经理发布,测试人员测试任务 id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); }); } private void Coding(string personName,string taskName) { Console.WriteLine($"{personName}开发{taskName} id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); Thread.Sleep(2000); } 3.2 async和awaitAsync 和 Await几乎与创建同步方法一样创建异步方法。代码:/// <summary> /// Async和Await应用 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnAsyncAndAwait_Click(object sender, EventArgs e) { Console.WriteLine($"btnAsyncAndAwait_Click start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); DoNothingAsync(); Console.WriteLine($"btnAsyncAndAwait_Click end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}"); } private async Task DoNothingAsync() { Console.WriteLine($"DoNothingAsync start id:{Th

0
0
0
浏览量2007
没晒干的咸鱼

C#多线程编程之线程池的使用 (ThreadPool)

在面向对象编程中,经常会面对创建对象和销毁对象的情况,如果不正确处理的话,在短时间内创建大量对象然后执行简单处理之后又要销毁这些刚刚建立的对象,这是一个非常消耗性能的低效行为,所以很多面向对象语言中在内部使用对象池来处理这种情况,以提高性能,比如在ADO.NET内部就允许使用数据库连接池来提高性能,在JDBC中没有提供数据库连接池,一些开发人员为了提高效率就自己编写数据库连接池来提高性能,当然据我所知在Java中有些框架提供了数据库连接的池化处理。在多线程编程时也会遇到上面的情况,如果创建了过多的线程将会增加操作系统资源的占用,并且还要处理资源要求和潜在的占用冲突,并且使用了多线程之后将使代码的执行流程和资源竞争情况变得复杂,稍不留心就会产生bug(在第二篇中在我写的代码中就曾经出现过一个bug,后来我自己发现并处理了这个bug)。在使用多线程编程时对需要同步的资源访问尤其需要注意,如系统资源(系统端口等)、共享资源(文件、窗口句柄等)、属于单个应用程序的资源(如全局、静态和实例字段或属性)。针对上面的情况,我们可以使用线程池来解决上面的大部分问题,跟使用单个线程相比,使用线程池有如下优点:1、缩短应用程序的响应时间。因为在线程池中有线程的线程处于等待分配任务状态(只要没有超过线程池的最大上限),无需创建线程。2、不必管理和维护生存周期短暂的线程,不用在创建时为其分配资源,在其执行完任务之后释放资源。3、线程池会根据当前系统特点对池内的线程进行优化处理。总之使用线程池的作用就是减少创建和销毁线程的系统开销。在.NET中有一个线程的类ThreadPool,它提供了线程池的管理。ThreadPool是一个静态类,它没有构造函数,对外提供的函数也全部是静态的。其中有一个QueueUserWorkItem方法,它有两种重载形式,如下:public static bool QueueUserWorkItem(WaitCallback callBack):将方法排入队列以便执行。此方法在有线程池线程变得可用时执行。public static bool QueueUserWorkItem(WaitCallback callBack,Object state):将方法排入队列以便执行,并指定包含该方法所用数据的对象。此方法在有线程池线程变得可用时执行。QueueUserWorkItem方法中使用的的WaitCallback参数表示一个delegate,它的声明如下:public delegate void WaitCallback(Object state)如果需要传递任务信息可以利用WaitCallback中的state参数,类似于ParameterizedThreadStart委托。下面是一个ThreadPool的例子,代码如下:using System.Threading; using System.Collections; using System.Diagnostics; using System; using System.ComponentModel; namespace ThreadPoolDemo { class ThreadPoolDemo1 { public ThreadPoolDemo1() { } public void Work() { ThreadPool.QueueUserWorkItem(new WaitCallback(CountProcess)); ThreadPool.QueueUserWorkItem(new WaitCallback(GetEnvironmentVariables)); } /// <summary> /// 统计当前正在运行的系统进程信息 /// </summary> /// <param name="state"></param> private void CountProcess(object state) { Process[] processes = Process.GetProcesses(); foreach (Process p in processes) { try { Console.WriteLine("Id:{0},ProcessName:{1},StartTime:{2}", p.Id, p.ProcessName, p.StartTime); } catch (Win32Exception e) { Console.WriteLine("ProcessName:{0}", p.ProcessName); } finally { } } Console.WriteLine("获取进程信息完毕。"); } /// <summary> /// 获取当前机器系统变量设置 /// </summary> /// <param name="state"></param> public void GetEnvironmentVariables(object state) { IDictionary list=System.Environment.GetEnvironmentVariables(); foreach (DictionaryEntry item in list) { Console.WriteLine("key={0},value={1}", item.Key, item.Value); } Console.WriteLine("获取系统变量信息完毕。"); } static void Main(string[] args) { ThreadPoolDemo1 tpd1 = new ThreadPoolDemo1(); tpd1.Work(); Thread.Sleep(5000); Console.WriteLine("OK"); Console.ReadLine(); } } }上面这段代码在本机的运行情况如下:key=Path,value=C:/WINDOWS/system32;C:/WINDOWS;C:/WINDOWS/System32/Wbem;C:/Program Files/Microsoft SQL Server/80/Tools/BINN;C:/Program Files/Microsoft SQL Server/80/Tools/Binn/;C:/Program Files/Microsoft SQL Server/90/DTS/Binn/;C:/Program Files/Microsoft SQL Server/90/Tools/binn/;C:/Program Files/Microsoft SQL Server/90/Tools/Binn/VSShell/Common7/IDE/;C:/Program Files/Microsoft Visual Studio 8/Common7/IDE/PrivateAssemblies/;C:/MySQL Server 5.1/bin;C:/PHP-5.2.9-Win32 key=TEMP,value=C:/DOCUME~1/ZHOUFO~1/LOCALS~1/Temp key=SESSIONNAME,value=Console key=PATHEXT,value=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.js;.JSE;.WSF;.WSH key=Rav,value=C:/Documents and Settings/All Users/Application Data/Rising/Rav key=PROCESSOR_ARCHITECTURE,value=x86 key=SystemDrive,value=C: key=APPDATA,value=C:/Documents and Settings/zhoufoxcn/Application Data key=windir,value=C:/WINDOWS key=USERPROFILE,value=C:/Documents and Settings/zhoufoxcn key=TMP,value=C:/DOCUME~1/ZHOUFO~1/LOCALS~1/Temp key=USERDOMAIN,value=ZHOU key=ProgramFiles,value=C:/Program Files key=FP_NO_HOST_CHECK,value=NO key=HOMEPATH,value=/Documents and Settings/zhoufoxcn key=COMPUTERNAME,value=ZHOU key=USERNAME,value=zhoufoxcn key=NUMBER_OF_PROCESSORS,value=2 key=PROCESSOR_IDENTIFIER,value=x86 Family 6 Model 15 Stepping 13, GenuineIntel key=SystemRoot,value=C:/WINDOWS key=ComSpec,value=C:/WINDOWS/system32/cmd.exe key=LOGONSERVER,value=//ZHOU key=CommonProgramFiles,value=C:/Program Files/Common Files key=PROMPT,value=$P$G key=PROCESSOR_LEVEL,value=6 key=PROCESSOR_REVISION,value=0f0d key=VS80COMNTOOLS,value=C:/Program Files/Microsoft Visual Studio 8/Common7/Tools/ key=lib,value=C:/Program Files/SQLXML 4.0/bin/ key=ALLUSERSPROFILE,value=C:/Documents and Settings/All Users key=VS90COMNTOOLS,value=c:/Program Files/Microsoft Visual Studio 9.0/Common7/Tools/ key=OS,value=Windows_NT key=HOMEDRIVE,value=C: 获取系统变量信息完毕。 Id:1864,ProcessName:mysqld,StartTime:2010-1-11 8:23:25 Id:3732,ProcessName:HTime,StartTime:2010-1-11 8:32:16 Id:1328,ProcessName:spoolsv,StartTime:2010-1-11 8:23:23 Id:3908,ProcessName:ctfmon,StartTime:2010-1-11 8:32:19 Id:3640,ProcessName:cmd,StartTime:2010-1-11 15:43:04 Id:1488,ProcessName:notepad,StartTime:2010-1-11 15:30:48 Id:3668,ProcessName:conime,StartTime:2010-1-11 15:27:22 Id:964,ProcessName:svchost,StartTime:2010-1-11 8:23:22 Id:1408,ProcessName:svchost,StartTime:2010-1-11 8:23:23 Id:1140,ProcessName:svchost,StartTime:2010-1-11 8:23:22 Id:1940,ProcessName:sqlbrowser,StartTime:2010-1-11 8:23:25 Id:1672,ProcessName:MsDtsSrvr,StartTime:2010-1-11 8:23:24 Id:3540,ProcessName:explorer,StartTime:2010-1-11 8:32:14 Id:3268,ProcessName:wps,StartTime:2010-1-11 14:08:00 Id:1568,ProcessName:inetinfo,StartTime:2010-1-11 8:23:24 Id:588,ProcessName:csrss,StartTime:2010-1-11 8:23:17 Id:3704,ProcessName:360tray,StartTime:2010-1-11 8:32:16 Id:1028,ProcessName:svchost,StartTime:2010-1-11 8:23:22 Id:2184,ProcessName:mqtgsvc,StartTime:2010-1-11 8:23:28 Id:2628,ProcessName:Reflector,StartTime:2010-1-11 15:04:35 Id:3872,ProcessName:devenv,StartTime:2010-1-11 15:26:51 Id:204,ProcessName:ThreadPoolDemo,StartTime:2010-1-11 15:43:26 Id:664,ProcessName:winlogon,StartTime:2010-1-11 8:23:20 Id:840,ProcessName:dexplore,StartTime:2010-1-11 14:50:21 Id:3900,ProcessName:rundll32,StartTime:2010-1-11 8:32:18 Id:1636,ProcessName:mdm,StartTime:2010-1-11 8:23:24 Id:1012,ProcessName:RavMonD,StartTime:2010-1-11 8:23:22 Id:1100,ProcessName:svchost,StartTime:2010-1-11 8:23:22 Id:476,ProcessName:smss,StartTime:2010-1-11 8:23:14 Id:920,ProcessName:svchost,StartTime:2010-1-11 8:23:21 Id:716,ProcessName:services,StartTime:2010-1-11 8:23:21 Id:3052,ProcessName:ThreadPoolDemo.vshost,StartTime:2010-1-11 15:42:27 Id:1448,ProcessName:msdtc,StartTime:2010-1-11 8:23:23 Id:180,ProcessName:mqsvc,StartTime:2010-1-11 8:23:25 Id:2512,ProcessName:iexplore,StartTime:2010-1-11 14:52:29 Id:1888,ProcessName:nvsvc32,StartTime:2010-1-11 8:23:25 Id:728,ProcessName:lsass,StartTime:2010-1-11 8:23:21 Id:2240,ProcessName:alg,StartTime:2010-1-11 8:23:28 Id:3808,ProcessName:jusched,StartTime:2010-1-11 8:32:17 Id:3128,ProcessName:RsTray,StartTime:2010-1-11 8:32:17 Id:1992,ProcessName:svchost,StartTime:2010-1-11 8:23:25 Id:2944,ProcessName:Foxit Reader,StartTime:2010-1-11 14:08:48 Id:4,ProcessName:System,StartTime:1601-1-1 8:00:00 Id:1604,ProcessName:jqs,StartTime:2010-1-11 8:23:24 ProcessName:Idle 获取进程信息完毕。在上面的代码中我们使用了线程池,并让它执行了两个任务,一个是列出系统当前所有环境变量的值,一个是列出系统当前运行的进程名和它们的启动时间。当然,优点和缺点总是同时存在的,使用ThreadPool也有一些缺点,使用线程池有如下缺点:1、一旦加入到线程池中就没有办法让它停止,除非任务执行完毕自动停止;2、一个进程共享一个线程池;3、要执行的任务不能有返回值(当然,线程中要执行的方法也是不能有返回值,如果确实需要返回值必须采用其它技巧来解决);4、在线程池中所有任务的优先级都是一样的,无法设置任务的优先级;5、不太适合需要长期执行的任务(比如在Windows服务中执行),也不适合大的任务;6、不能为线程设置稳定的关联标识,比如为线程池中执行某个特定任务的线程指定名称或者其它属性。如果我们要面临的情况正好是线程池的缺点,那么我们只好继续使用线程而不是线程池。不过在某些情况下使用线程池确实可以带来很多方便的,比如在WEB服务器中,可以使用线程池来处理来自客户端的请求,可以以比较高的性能运行。

0
0
0
浏览量2009
没晒干的咸鱼

C#异步编程(async and await)及异步方法同步调用

1、什么是异步?异步操作通常用于执行完成时间可能较长的任务,如打开大文件、连接远程计算机或查询数据库=异步操作在主应用程序线程以外的线程中执行。应用程序调用方法异步执行某个操作时,应用程序可在异步方法执行其任务时继续执行。2、同步与异步的区别同步(Synchronous):在执行某个操作时,应用程序必须等待该操作执行完成后才能继续执行。异步(Asynchronous):在执行某个操作时,应用程序可在异步操作执行时继续执行。实质:异步操作,启动了新的线程,主线程与方法线程并行执行。3、异步和多线程的区别我们已经知道, 异步和多线程并不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段。异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。实现异步可以采用多线程技术或则交给另外的进程来处理。简单的说就是:异步线程是由线程池负责管理,而多线程,我们可以自己控制,当然在多线程中我们也可以使用线程池。就拿网络扒虫而言,如果使用异步模式去实现,它使用线程池进行管理。异步操作执行时,会将操作丢给线程池中的某个工作线程来完成。当开始I/O操作的时候,异步会将工作线程还给线程池,这意味着获取网页的工作不会再占用任何CPU资源了。直到异步完成,即获取网页完毕,异步才会通过回调的方式通知线程池。可见,异步模式借助于线程池,极大地节约了CPU的资源。注:DMA(Direct Memory Access)直接内存存取,顾名思义DMA功能就是让设备可以绕过处理器,直接由内存来读取资料。通过直接内存访问的数据交换几乎可以不损耗CPU的资源。在硬件中,硬盘、网卡、声卡、显卡等都有直接内存访问功能。异步编程模型就是让我们充分利用硬件的直接内存访问功能来释放CPU的压力。两者的应用场景:计算密集型工作,采用多线程。IO密集型工作,采用异步机制。C#异步代码参考(async and await)using System; using System.Threading; using System.Threading.Tasks; namespace AsyncAwaitDemo { class Program { static void Main(string[] args) { Console.WriteLine("main start.."); AsyncMethod(); Thread.Sleep(1000); Console.WriteLine("main end.."); Console.ReadLine(); } static async void AsyncMethod() { Console.WriteLine("start async"); var result = await MyMethod(); Console.WriteLine("end async"); //return 1; } static async Task<int> MyMethod() { for (int i = 0; i < 5; i++) { Console.WriteLine("Async start:" + i.ToString() + ".."); await Task.Delay(1000); //模拟耗时操作 } return 0; } } } 使用Wait()和GetAwaiter().GetResult()方法实现异步方法同步执行using System; using System.Threading.Tasks; namespace AsyncTest { class Program { static void Main(string[] args) { Console.WriteLine("Async Test job:"); Console.WriteLine("main start.."); Console.WriteLine("MyMethod()异步方法同步执行:"); MyMethod().Wait();//在main中等待async方法执行完成 int i = MyMethod().GetAwaiter().GetResult();//用于在main中同步获取async方法的返回结果 Console.WriteLine("i:" + i); Console.WriteLine("main end.."); Console.ReadKey(true); } static async Task<int> MyMethod() { for (int i = 0; i < 5; i++) { Console.WriteLine("Async start:" + i.ToString() + ".."); await Task.Delay(1000); //模拟耗时操作 } return 0; } } } C# 异步编程 异步委托调用同步方法同步化操作:由前后紧接的组件或函数调用组成。一个同步化调用会阻塞整个进程直到这一个操作完成。异步化操作:不会阻塞启动操作的调用线程。调用程序必须通过轮流检测、软件中的中断信号或只是明确地等待完成信号来发现调用的完成。.NET 为异步操作提供两种设计模式:使用 IAsyncResult 对象的异步操作。使用事件的异步操作。.NET的许多方面都支持异步编程功能,这些方面包括:文件 IO、流 IO、套接字 IO。网络。远程处理信道(HTTP、TCP)和代理。使用 ASP.NET 创建的 XML Web services。ASP.NET Web 窗体。使用 MessageQueue 类的消息队列。异步委托提供以异步方式调用同步方法的能力。当同步调用一个委托时,调用方法直接对当前线程调用目标方法。如果编译器支持异步委托,则它将生成该调用方法以及BeginInvoke 和EndInvoke 方法。如果调用BeginInvoke方法,则公共语言运行库将对请求进行排队并立即返回到调用方。将对来自线程池的线程调用该目标方法。提交请求的原始线程自由地继续与目标方法并行执行,该目标方法是对线程池线程运行的在回调中,使用EndInvoke 方法来获取返回值和输入/输出参数。如果没有对BeginInvoke指定回调,则可以在提交请求的原始线程上使用EndInvoke。using System; using System.Threading; using System.Runtime.Remoting.Messaging; namespace ClassMain { //委托声明(函数签名) delegate string MyMethodDelegate(); class MyClass { //要调用的动态方法 public string MyMethod1() { return "Hello Word1"; }

0
0
0
浏览量2006
没晒干的咸鱼

C#异步编程之async/await

异步编程场景解决耗时任务占用主线程执行的问题。因此耗时高的任务适用于异步编程,C#中有以下场景:对于 I/O 绑定代码,等待一个在 async 方法中返回 Task 或 Task<T> 的操作。对于 CPU 绑定代码,等待一个使用 Task.Run 方法在后台线程启动的操作。如何判断是CPU耗时操作还是IO耗时操作呢?简单来说,如果需要等待某些内容(如数据库中的数据、大文本文件的读取等),则为IO耗时操作;如果需要执行大量的计算,则属于CPU耗时操作。async awaitasync/await成对出现,用于简化异步编程。async出现在方法的声明前,表示这是一个异步方法。await则出现在这个异步方法的内部,一般在Task前。async/await关键字本身不会创建新线程,但被await的方法内部一般是通过创建线程来实现。微软官方给定的异步方法执行顺序:到底异步还是同步?这个标题算是个问题?问题又不太对。async/await成对出现的方法是异步方法,那哪来的同步呢?实际上最好先从多线程说起,关于异步编程这块,我准备整理一个系列出来。顺序上可能是这篇博客先出来,那就用下面的例子先展示一下。我看到有的文章说,async/await既可以是异步,又可以是同步。所以就会有疑惑,到底是同步还是异步呢?看下面两个例子。异步方式执行两个Task输出并没有按顺序,且并没有按主线程中代码顺序来执行。可以看到,两个异步方法尚未输出完成,主方法中main end.....已经输出。且Method1和Method2中的输出也是交替来的。输出结果:static void Main() { Console.WriteLine("main start...."); AsyncMethod1(); AsyncMethod2(); Thread.Sleep(1000); Console.WriteLine("main end....."); Console.ReadLine(); } static async void AsyncMethod1() { Console.WriteLine("start AsyncMethod1 async"); var result = await MyMethod1(); Console.WriteLine("end AsyncMethod1 async"); } static async Task<int> MyMethod1() { for(int i = 0; i < 5; i++) { Console.WriteLine("Async MyMethod1 start:" + i.ToString()); await Task.Delay(1000); } return 0; } static async void AsyncMethod2() { Console.WriteLine("start AsyncMethod2 async"); var result = await MyMethod2(); Console.WriteLine("end AsyncMethod2 async"); } static async Task<int> MyMethod2() { for(int i = 0; i < 5; i++) { Console.WriteLine("Async MyMethod2 start:" + i.ToString()); await Task.Delay(1000); } return 0; } 异步方法同步执行结合上面那个例子,再看这个例子的输出,就容易理解所谓的异步方法同步执行了。意思是说,Method1和Method2两个方法是异步执行的(理解成创建一个新线程去执行的吧)。但是,通过一些手段,让这两个任务按顺序来执行。即Method1执行完再来执行Method2。两个Task相对主线程来说,是异步去执行的,但这个异步也是按顺序来的。Method1输出完了,再执行Method2的输出static void Main() { Console.WriteLine("Async Test job"); Console.WriteLine("main start....."); Console.WriteLine("MyMethod() 异步方法同步执行:"); int i = MyMethod().GetAwaiter().GetResult(); int j = MyMethod2().GetAwaiter().GetResult(); Console.WriteLine($" i : {i}"); Console.WriteLine($" j : {j}"); Console.WriteLine("main end ....."); Console.ReadLine(); } static async Task<int> MyMethod() { for(int i = 0; i < 5; i++) { Console.WriteLine("Async Method start: " + i.ToString()); await Task.Delay(1000); } return 0; } static async Task<int> MyMethod2() { for(int i = 0; i < 5; i++) { Console.WriteLine("Async Method2 start: " + i.ToString()); await Task.Delay(1000); } return 0; } 何时使用async/awaitasp.net mvc中使用async/await并不能提升访问速度。但使用异步编程,可以提高响应能力(吞吐量),即使用异步方式在同一时间可以处理更多的请求。使用同步方式,线程会被耗时操作一直占用,直到耗时操作结束;使用异步方式,程序走到await关键字会立即return,释放线程,剩下的代码将放到一个回调(Task.GetAwaiter()的UnsafeOnCompleted(Action)),耗时操作完成时才会回调执行。在web服务器上, .NET Framework维护用于处理ASP.NET请求的线程池。当请求到达时,将调度线程池中的线程以处理该请求。如果以同步方式处理请求,则处理请求的线程将在处理请求时处于繁忙状态,并且该线程无法处理其他请求。在启动时看到大量并发请求的web应用中,或具有突发负载(其中并发增长突然增加)时,使web服务器调用异步会提高应用程序的相应能力。异步请求与同步请求所需的处理时间相同。如果请求发出需要两秒时间才能完成web服务调用,则该请求将需要两秒钟,无论是同步执行还是异步执行。但是,在异步调用期间,线程在等待第一个请求完成时不会被阻止响应其他请求。因此,当有多个并发请求调用长时间运行的操作时,异步请求会组织请求队列和线程池的增长。同步方式执行耗时任务 public ActionResult Index() { DateTime startTime = DateTime.Now;//进入DoSomething方法前的时间 var startThreadId = Thread.CurrentThread.ManagedThreadId;//进入DoSomething方法前的线程ID DoSomething();//耗时操作 DateTime endTime = DateTime.Now;//完成DoSomething方法的时间 var endThreadId = Thread.CurrentThread.ManagedThreadId;//完成DoSomething方法后的线程ID return Content($"startTime:{ startTime.ToString("yyyy-MM-dd HH:mm:ss:fff") } startThreadId:{ startThreadId }\nendTime:{ endTime.ToString("yyyy-MM-dd HH:mm:ss:fff") } endThreadId:{ endThreadId }"); } /// <summary> /// 耗时操作 /// </summary> /// <returns></returns> private void DoSomething() { Thread.Sleep(10000); } 异步方式执行耗时任务 public async Task<ActionResult> Index() { DateTime startTime = DateTime.Now;//进入DoSomething方法前的时间 var startThreadId = Thread.CurrentThread.ManagedThreadId;//进入DoSomething方法前的线程ID await DoSomething();//耗时操作 DateTime endTime = DateTime.Now;//完成DoSomething方法的时间 var endThreadId = Thread.CurrentThread.ManagedThreadId;//完成DoSomething方法后的线程ID return Content($"startTime:{ startTime.ToString("yyyy-MM-dd HH:mm:ss:fff") } startThreadId:{ startThreadId }\nendTime:{ endTime.ToString("yyyy-MM-dd HH:mm:ss:fff") } endThreadId:{ endThreadId }"); } /// <summary> /// 耗时操作 /// </summary> /// <returns></returns> private async Task DoSomething() { await Task.Run(() => Thread.Sleep(10000)); } 上面说了那么多,那到底该不该用async/await,什么时候用他俩?总结:对于计算密集型工作,使用多线程对于IO密集型工作,采用异步机制

0
0
0
浏览量2005
没晒干的咸鱼

.NET通过async/await实现并行

如果可以并行可以大大提高性能,但在我们的使用中,不可能全是并行的也是要有线行操作,所以我们需要在业务逻辑层进行并行操作的护展:数据访问层不变还是以前一样如下:public class UserDAL { public User GetUser() { User user = new User(); user.Name = "N1"; user.Address = "A1"; return user; } public List<User> GetUserList() { List<User> list = new List<User>(); list.Add(new User { Name = "N11", Address = "A11" }); list.Add(new User { Name = "N12", Address = "A12" }); return list; } } 业务逻辑层需要护展有async,如下:public class UserBLL { UserDAL dal = new UserDAL(); public async Task<User> GetUserAsync() { return await Task.Run(() => { return dal.GetUser(); }); } public User GetUser() { return dal.GetUser(); } public async Task<List<User>> GetUserListAsync() { return await Task.Run(() => { return dal.GetUserList(); }); } public List<User> GetUserList() { return dal.GetUserList(); } } 最后是调用了,如下:UserBLL userBLL = new UserBLL(); public async Task<ActionResult> Index() { var user = userBLL.GetUserAsync(); var listUser = userBLL.GetUserListAsync(); int t1 = Environment.TickCount; await Task.WhenAll(user, listUser); ViewBag.User = user.Result; ViewBag.ListUser = listUser.Result;         //ViewBag.User = await user;         //ViewBag.ListUser = await listUser; ViewBag.Times = Environment.TickCount - t1; return View(); } 具体的性能我们可以测试,在数据访问层每次延时500毫秒,用并行和线行测试一下,并行大约时间在500毫秒左右,而线行则在1000毫秒左右C#异步编程再续 async异步方法与同步方法的并行今天晚上没事写了个测试的代码,又看了看.net的并行编程,两个方法,一个是异步async修饰的,另外一个是普通的方法,在控制台程序的Main方法里去调用这两个方法,会有什么结果呢?编程首先咱们看一下方法的组成,step1以下异步 public async void Step1() { try { //await进行等待后,新线程的异常能够被主线程捕捉,这是正常的,下面的代码不会被执行 await Task.Run(() => { Console.WriteLine("Step1 Current ThreadID" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(3000); }); await Task.Run(() => { Console.WriteLine("Step1 Current ThreadID" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("ThreadTest.Test Runing"); }); } catch (Exception ex) { Console.WriteLine("ThreadTest" + ex.Message); } } step2以下async public void Step2() { Console.WriteLine("Step2 Current ThreadID" + Thread.CurrentThread.ManagedThreadId); } 咱们能够看到step2很简单,不会有什么延时,就是在屏幕上输出一段话,而step1就显得复杂一些了,它是一个异步的方法,而且使用Task.Run开启了两个新线程,而第一测试个线程的运行时间是3秒,很长呀,哈哈,第二个是在屏幕上输出一段话!如今咱们把step1和step2写在一块儿会出现什么状况呢?spa var test = new ThreadTest(); test.Step1();//整个方法不阻塞,但方法内部有可能阻塞 test.Step2(); 经过上面的图咱们认识到了,step1按着顺序先执行,而因为第一个线程要执行3秒,这时step2被并行执行,3秒后,step1的第二个线程继续执行(因为使用了await,因此step1内部进行了等待,不会应响他外面的方法,也应响不了,呵呵!.net怎么了,看了上面的例如,是否是对并行编程有了新的认识呢!线程C# 同步转异步 TaskCompletionSource当我们遇到一些异步执行又无法等待时的逻辑,比如动画的执行。而业务上又需要等待逻辑的完成,再去处理后续的操作。这时需要转成异步方法如下,同步执行一个动画后,再输出日志:private async void TaskCompleteSourceAwait_OnClick(object sender, RoutedEventArgs e) { bool isCompleted = await AwaitByTaskCompletionAsync(new Storyboard() { Duration = new Duration(TimeSpan.FromSeconds(2)) }); Debug.WriteLine($"TaskCompleteSourceAwait_OnClick end:{isCompleted}"); } 通过TaskCompletionSource如何转化为异步方法,并等待动画完成?/// <summary> /// 执行动画 /// </summary> /// <param name="storyboard"></param> /// <returns></returns> public static async Task<bool> AwaitByTaskCompletionAsync(Storyboard storyboard) { var taskCompletionSource = new TaskCompletionSource<bool>(); Debug.WriteLine("Storyboard start"); storyboard.Completed += OnStoryboardCompleted; storyboard.Begin(); void OnStoryboardCompleted(object sender, EventArgs e) { Debug.WriteLine("Storyboard end"); storyboard.Completed -= OnStoryboardCompleted; taskCompletionSource.SetResult(true); } return await taskCompletionSource.Task; } 测试效果:

0
0
0
浏览量2007