利用 async & await 进行异步 IO 操作
目录
- 使用异步特性实现 IO 操作的意义
- 使用带异步的 FileStream 类
- 异步写入文本
- 异步读取文本
- 并行异步 I/O
使用异步特性实现 IO 操作的意义
- 异步特性有利于增强 App 的响应能力。因为一个操作的 UI 线程可以执行其他工作。如果 UI 线程需要执行较长时间的代码(如 > 1s),UI 会阻塞到 I/O 完成,这时用户界面线程才可以重新处理键盘、鼠标输入和其他操作。
- 在本地进行文件访问也许效率非常高,但是,假如该文件在远程的服务器上呢?
- 使用异步额外增加的开销很小,不大。
- 异步任务可以并行运行。
使用带异步的 FileStream 类
有一个参数 useAsync,可以避免在许多情况下阻塞线程池的线程。可以通过useAsync = true 来进行启用或在构造函数中进行参数调用。
但是,如果你想使用该参数 useAsync,则需要自己新建一个 FileStream 对象。请注意,异步调用是在 UI 中的,即使线程池线程阻塞,在 await 期间,用户界面线程也不会被阻塞。
异步写入文本
在每个 await 语句中,都会立即退出。当文件 I/O 完成时,方法会执行 await 语句后面的语句。请注意异步修饰符在使用 await 语句方法的定义。
1 private async void btnWrite_Click(object sender, RoutedEventArgs e)
2 {
3 await WriteTextAsync();
4 }
btnWrite_Click
1 /// <summary>
2 /// 异步写入文件
3 /// </summary>
4 /// <returns></returns>
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;
使用等待的第二个语句(行号3、4)导致方法立即退出并返回其他任务。当随后处理的文件完成时,执行回 await 的语句。
异步读取文本
放入 StringBuilder。不同于在前面的示例,会不断 await 一个读取的长度值。ReadAsync 方法返回 Task<Int32>,即 Task<int>,因此,等待的计算生成一个 Int32 值(numRead),在操作完成之后。
1 /// <summary>
2 /// 异步读取文本
3 /// </summary>
4 /// <param name="fileName"></param>
5 /// <returns></returns>
6 private async Task<string> 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 }
btnRead_Click
并行异步 I/O
对于每个文件,WriteAsync 方法返回后将被添加到任务列表的集合中。在处理完成所有的任务时,await Task.WhenAll(tasks); 语句将退出方法并恢复执行。
finally 块的将所有 FileStream 实例进行清理回收。如果直接在 using 语句中创建 FileStream 实例,则 FileStream 实例可能在任务完成之前就被处理。
【注意】所有性能提高几乎完全是异步并行处理。异步的优点是它不会占用多个线程,也就是说,它不会占用用户界面线程。
1 /// <summary>
2 /// 异步写入多个文件
3 /// </summary>
4 /// <param name="folder"></param>
5 /// <returns></returns>
6 private async Task WriteMultiTextAsync(string folder)
7 {
8 var tasks = new List<Task>();
9 var fileStreams = new List<FileStream>();
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 }
btnWriteMulti_Click
在使用 WriteAsync 和 ReadAsync 方案时,你可以指定 CancellationToken,来在中途取消操作。