欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

多线程编程学习笔记——使用异步IO(一)

程序员文章站 2022-03-28 15:13:23
假设以下场景,如果在客户端运行程序,最的事情之一是有一个响应的用户界面。这意味着无论应用程序发生什么,所有的用户界面元素都要保持 快速运行,用户能够从应用程序得到快速响应。达到这一点并不容易!如果你尝试在Windows系统中打开记事本并加载一个有几兆大小的文档,应用程序窗口将交结一段的时间,因为整个... ......

接上文 多线程编程学习笔记——使用并发集合(一)

接上文 多线程编程学习笔记——使用并发集合(二)

接上文 多线程编程学习笔记——使用并发集合(三)

 

         假设以下场景,如果在客户端运行程序,最的事情之一是有一个响应的用户界面。这意味着无论应用程序发生什么,所有的用户界面元素都要保持 快速运行,用户能够从应用程序得到快速响应。达到这一点并不容易!如果你尝试在Windows系统中打开记事本并加载一个有几兆大小的文档,应用程序窗口将交结一段的时间,因为整个文件要先从硬盘中加载,然后程序才能开始处理用户输入。

        这是一个非常重要的问题,在这种情况下,唯一方案是无论如何都要避免阻塞UI纯种。这反过来意味着为了防止阻塞UI线程,每个与UI有关的API必须只被允许异步调用 。这是Windows操作系统重新升级API的关键原因 ,其几乎把每个方法替换为异步方式。但是应用程序使用多线程来达到此目的会影响性能吗?当然会。然而考虑到只有一个用户,那么这是划算的。如果应用程序可以使用电脑的所有能力从而变得更加高效,而且这种能力 只为运行程序的唯一用户服务,这是好事。

         接下来看看第二种情况。如果程序运行在服务器端,则是完全不同的情形。可伸缩性是最高优先级,这意味着单个 用户消耗越少的资源越好。如果为每个用户创建多个线程,则可伸缩性并不好。以高效的方式来平衡应用程序资源的消耗是个非常复杂的问题。例如,在ASP.NET中,我们使用工作线程池来服务客户端请求。这个池的工作线程是有限的,所以不得不最小化每个工作线程的使用时间以便达到高伸缩性。这意味着需要把工作线程越快越好地放回到池中,从而可以服务下一个请求。如果我们启动了一个需要计算的异步操作,则整个工作流程会很低效。首先从线程池中取出一个工作 线程用以服务客户端请求。然后取出另一个工作线程并开始处理异步操作。现在有两个工作线程都在处理请求,如果第一个线程能做些有用的事则非常好。可惜,通常情况下,我们简单等待异步 操作完成,但是我们却消费了两个工作 线程,而不是一个。在这个场景中,异步 比同步执行实际上更糟糕!我们不需要使用所有CPU核心,因为我们已经在服务很多客户端,它们已经使用了CPU的所有计算能力。我们无须保持第一个线程响应,因为这没有用户界面。那么为什么我们应该在服务端使用异步呢?

        答案是只有异步输入/输出操作才应用使用异步。目前,现代计算机通过有一个磁盘驱动器来存储文件,一块网卡来通过网络发送与接收数据。所有这些设备都有自己的芯片,以非常底层的方式来管理输入/输出操作并发信号 给操作系统。这种执行I/O任务的方式被称为I/O线程。

          在ASP.NET中,一旦有一个异步的I/O操作在工作线程开始时,它会被立即返回到线程池中。当这个操作继续运行时,这个线程可以服务其他的客户端。最终,当操作发出信号完成时,ASP.NET基础设施从线程池中获取一个空闲的工作线程,然后会完成这个操作。

一、   异步使用文件

      本救命学习如何使用异步的方式读写一个文件。

 1.示例代码如下。

 

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace ThreadIODemo
{
    class Program
    {

        static void Main(string[] args)
        {
            Console.WriteLine("--开始 使用 异步 I/O 线程 -- ");
            var t = ReadWriteAsyncIO();
            t.GetAwaiter().GetResult();
            Console.Read();
        }

        const int BUFFER_SIZE = 4096;

        async static Task ReadWriteAsyncIO()
        {

            using (var fs = new FileStream("test1.txt", FileMode.Create, FileAccess.ReadWrite, FileShare.None, BUFFER_SIZE))
            {

                Console.WriteLine("1. 使用 I/O 线程 是否异步:{0}",fs.IsAsync);
                byte[] buffer = Encoding.UTF8.GetBytes(CreateFileContent());

                var writeTask = Task.Factory.FromAsync(fs.BeginWrite, fs.EndWrite, buffer, 0, buffer.Length, null);
                await writeTask;

            }

            using (var fs = new FileStream("test2.txt", FileMode.Create, FileAccess.ReadWrite, FileShare.None,
BUFFER_SIZE, FileOptions.Asynchronous)) { Console.WriteLine("2. 使用 I/O 线程 是否异步:{0}",fs.IsAsync); byte[] buffer = Encoding.UTF8.GetBytes(CreateFileContent()); var writeTask = Task.Factory.FromAsync(fs.BeginWrite, fs.EndWrite, buffer, 0, buffer.Length, null); await writeTask; } using (var fs = File.Create("test3.txt", BUFFER_SIZE, FileOptions.Asynchronous)) { using (var sw = new StreamWriter(fs)) { Console.WriteLine("3. 使用 I/O 线程 是否异步:{0}",fs.IsAsync); await sw.WriteAsync(CreateFileContent()); } } using (var sw = new StreamWriter("test4.txt", true)) { Console.WriteLine("4. 使用 I/O 线程 是否异步:{0}",((FileStream)sw.BaseStream).IsAsync); await sw.WriteAsync(CreateFileContent()); } System.Threading.Thread.Sleep(1000); Console.WriteLine("开始异步读取文件"); Task<long>[] readTasks = new Task<long>[4]; for (int i = 0; i < 4; i++) { readTasks[i] = SumFileContent(string.Format("test{0}.txt",i + 1)); } long[] sums = await Task.WhenAll(readTasks); Console.WriteLine("所有文件中的和值:{0}", sums.Sum()); Console.WriteLine("开始删除文件"); Task[] delTasks = new Task[4]; for (int i = 0; i < 4; i++) { string filename = string.Format("test{0}.txt",i + 1); delTasks[i] = SimulateAsynchronousDelete(filename); } await Task.WhenAll(delTasks); Console.WriteLine("删除文件结束"); } static string CreateFileContent() { var sb = new StringBuilder(); for (int i = 0; i < 100000; i++) { sb.AppendFormat("{0}", new Random(i).Next(0, 99999)); sb.AppendLine(); } return sb.ToString(); } async static Task<long> SumFileContent(string filename) { using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None, BUFFER_SIZE, FileOptions.Asynchronous)) using (var sr = new StreamReader(fs)) { long sum = 0; while (sr.Peek() > -1) { string line = await sr.ReadLineAsync(); sum += long.Parse(line); } return sum; } } static Task SimulateAsynchronousDelete(string filename) { return Task.Run(() => File.Delete(filename)); } } }

 

2.程序运行结果,如下图。

 多线程编程学习笔记——使用异步IO(一)

      当程序运行时,我们以不同的方式创建了4个文件,并写入一些随机数据。

      在第一个例子中,使用的是FileStream类以及其方式,将异步编程模式API转换成任务。

      在第二个例子中,使用的是FileStream类以及其方式,不过在构造的时候提供了FileStream.Asynchronous参数 。

      在第三个例子使用了一些简化的API,比如File.Create方法和StreamWrite类。它也使用I/O线程,我们可以使用Stream.iSaSYNC属性来检查。

     在第四个例子说明了过分简化 也不好。这里我们借助异步委托调用来模拟异步I/O,其实并没有使用异步I/O。

      然后并行地异步地从所有文件中读取数据,统计每个文件内容,然后求总和。

        最后,删除所有文件。