C#使用异步实现 IO 操作

  • Post author:
  • Post category:其他


使用异步特性实现 IO 操作的意义

异步特性有利于增强应用程序的响应能力。因为一个操作的 UI 线程可以执行其他工作。如果 UI 线程需要执行较长时间的代码(如,> 50ms),UI 会阻塞到 I/O 完成,这时用户界面线程才可以重新处理键盘、鼠标输入和其他操作。

文件访问操作的延迟在本地也许非常低,但是,我们可以考虑一下文件在非本地时进行的操作。例如,文件可能会存放位于远程的服务器。

使用异步额外增加的开销很小。

异步任务可以并行运行。

使用 FileStream 类

示例使用 FileStream 类,它具有一个 option 导致异步 I/O 发生在操作系统级别。使用此 option,您可以避免在许多情况下阻塞线程池线程。若要启用该 option,则指定 useAsync = true 或在构造函数中进行参数调用。

不能设置 StreamReader 和 StreamWriter 的 option,如果直接通过指定文件路径打开这些文件。但是,您如果想使用该 option,则提供自己 FileStream 类打开的 Stream。 请注意,异步调用是在 UI app,即使线程池线程阻塞,在 await 期间,用户界面线程也不会被阻塞。

编写文本

下面的示例是将文本写入到文件。在每个 await 语句中,都会立即退出。当文件 I/O 完成时,方法会执行 await 语句后面的语句。请注意异步修饰符在使用 await 语句方法的定义。

1 private async void btnWrite_Click(object sender, RoutedEventArgs e)

2 {

3 await WriteTextAsync();

4 }

复制代码

1 ///

2 /// 异步写入文件

3 ///

4 ///

5 private async Task WriteTextAsync()

6 {

7 var path = $”temp.txt”;

8 var content = Guid.NewGuid().ToString();

9

10 using (var fs = new FileStream(path,

11 FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, bufferSize: 4096, useAsync: true))

12 {

13 var buffer = Encoding.UTF8.GetBytes(content);

14

15 //var writeTask = fs.WriteAsync(buffer, 0, buffer.Length);

16 //await writeTask;

17 await fs.WriteAsync(buffer, 0, buffer.Length);

18 }

19 }

复制代码

行号 17 的语句可以修改为:

1 //await fs.WriteAsync(buffer, 0, buffer.Length);

2 //可以改为

3 var writeTask = fs.WriteAsync(buffer, 0, buffer.Length);

4 await writeTask;

第一个语句(行号 1)返回任务并导致进程的文件。使用等待的第二个语句(行号3、4)导致方法立即退出并返回其他任务。当随后处理的文件完成时,执行回 await 的语句。

读取文本

下面的示例是从文件中读取文本。将文本缓冲区的内容放入 StringBuilder。不同于在前面的示例,会不断 await 一个读取的长度值。ReadAsync 方法返回 Task,即 Task,因此,等待的计算生成一个 Int32 值(numRead),在操作完成之后。

复制代码

1 ///

2 /// 异步读取文本

3 ///

4 ///

5 ///

6 private async Task ReadTextAsync(string fileName)

7 {

8 using (var fs = new FileStream(fileName,

9 FileMode.OpenOrCreate, FileAccess.Read, FileShare.None, bufferSize: 4096, useAsync: true))

10 {

11 var sb = new StringBuilder();

12 var buffer = new byte[0x1000]; //十六进制 等于十进制的 4096

13 var readLength = 0;

14

15 while ((readLength = await fs.ReadAsync(buffer, 0, buffer.Length)) != 0)

16 {

17 var text = Encoding.UTF8.GetString(buffer, 0, readLength);

18 sb.Append(text);

19 }

20

21 return sb.ToString();

22 }

23 }

1 private async void btnRead_Click(object sender, RoutedEventArgs e)

2 {

3 var fileName = “temp.txt”;

4 if (!File.Exists(fileName))

5 {

6 Debug.WriteLine($”文件找不到:{fileName}”);

7 return;

8 }

9

10 try

11 {

12 var content = await ReadTextAsync(fileName);

13 Debug.WriteLine(content);

14 }

15 catch (Exception ex)

16 {

17 Debug.WriteLine(ex.Message);

18 }

19 }

复制代码

并行异步 I/O

下面的示例通过编写多个文本文件进行演示并行处理。对于每个文件,WriteAsync 方法返回后将被添加到任务列表的集合中。在处理完成所有的任务时,await Task.WhenAll(tasks); 语句将退出方法并恢复执行。

在任务完成后,进入 finally 块的将所有 FileStream 实例进行清理回收。如果直接在 using 语句中创建 FileStream 实例,则 FileStream 实例可能在任务完成之前就被处理。

【注意】所有性能提高几乎完全是异步并行处理。异步的优点是它不会占用多个线程,也就是说,它不会占用用户界面线程。

复制代码

1 ///

2 /// 异步写入多个文件

3 ///

4 ///

5 ///

6 private async Task WriteMultiTextAsync(string folder)

7 {

8 var tasks = new List();

9 var fileStreams = new List();

10

11 try

12 {

13 for (int i = 1; i <= 10; i++)

14 {

15 var fileName = Path.Combine(folder, $”{i}.txt”);

16 var content = Guid.NewGuid().ToString();

17 var buffer = Encoding.UTF8.GetBytes(content);

18

19 var fs = new FileStream(fileName,

20 FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, bufferSize: 4096, useAsync: true);

21 fileStreams.Add(fs);

22

23 var writeTask = fs.WriteAsync(buffer, 0, buffer.Length);

24 tasks.Add(writeTask);

25 }

26

27 await Task.WhenAll(tasks);

28 }

29 finally

30 {

31 foreach (var fs in fileStreams)

32 {

33 fs.Close();

34 fs.Dispose();

35 }

36 }

37 }

复制代码

复制代码

1 private async void btnWriteMulti_Click(object sender, RoutedEventArgs e)

2 {

3 var folder = $”temp”;

4

5 if (!Directory.Exists(folder))

6 {

7 Directory.CreateDirectory(folder);

8 }

9

10 await WriteMultiTextAsync(folder);

11 }

复制代码

在使用 WriteAsync 和 ReadAsync 方案时,你可以指定 CancellationToken,来在中途取消操作。