7. 使用异步进行文件访问¶
可使用异步功能访问文件。 通过使用异步功能,你可以调用异步方法而无需使用回调,也不需要跨多个方法或 lambda 表达式来拆分代码。 若要使同步代码异步,只需调用异步方法而非同步方法,并向代码中添加几个关键字。
可能出于以下原因向文件访问调用中添加异步:
异步使 UI 应用程序响应速度更快,因为启动该操作的 UI 线程可以执行其他操作。 如果 UI 线程必须执行耗时较长的代码(例如超过 50 毫秒),UI 可能会冻结,直到 I/O 完成,此时 UI 线程可以再次处理键盘和鼠标输入及其他事件。
异步可减少对线程的需要,进而提高 ASP.NET 和其他基于服务器的应用程序的可伸缩性。 如果应用程序对每次响应都使用专用线程,同时处理 1000 个请求时,则需要 1000 个线程。 异步操作在等待期间通常不需要使用线程。 异步操作仅需在结束时短暂使用现有 I/O 完成线程。
当前条件下,文件访问操作的延迟可能非常低,但以后可能大幅增加。 例如,文件可能会移动到覆盖全球的服务器。
使用异步功能所增加的开销很小。
异步任务可以轻松地并行运行。
在下面的示例中,包括以下 using 语句。
1 2 3 4 5 6 | using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
|
7.1. 使用 FileStream 类¶
本主题中的示例使用 FileStream 类,该类包含可导致在操作系统级别出现异步 I/O 的选项。 使用此选项可避免在许多情况下阻止 ThreadPool 线程。 若要启用此选项,可在构造函数调用中指定 useAsync=true 或 options=FileOptions.Asynchronous 参数。
如果通过指定文件路径直接打开 StreamReader 和 StreamWriter,则无法将此选项与这二者配合使用。 但是,如果为二者提供已由 FileStream 类打开的 Stream,则可以使用此选项。 请注意,即使 ThreadPool 线程受到阻止,UI 应用中的异步调用仍然更快,因为 UI 线程在等待期间不会受到阻止。
7.2. 编写文本¶
下面的示例将文本写入文件。 在每个 await 语句中,该方法会立即退出。 文件 I/O 完成后,该方法将在 await 语句后面的语句中继续。 请注意,async 修饰符在使用 await 语句的方法的定义中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public async Task ProcessWriteAsync()
{
string filePath = @"temp2.txt";
string text = "Hello World\r\n";
await WriteTextAsync(filePath, text);
}
private async Task WriteTextAsync(string filePath, string text)
{
byte[] encodedText = Encoding.Unicode.GetBytes(text);
using (FileStream sourceStream = new FileStream(filePath,
FileMode.Append, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true))
{
await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
};
}
|
原始示例包含 await sourceStream.WriteAsync(encodedText, 0, encodedText.Length); 语句,它是下面两个语句的缩写式:
1 2 | Task theTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
await theTask;
|
第一条语句返回任务,并会导致文件处理启动。 具有 await 的第二条语句将使方法立即退出并返回一个不同的任务。 文件处理稍后完成后,执行将返回到 await 后面的语句中
7.3. 读取文本¶
下面的示例读取文件中的文本。 将会缓冲文本,并且在此情况下,会将其放入 StringBuilder。 与前一示例不同,await 的计算将生成一个值。 ReadAsync 方法返回 Task<Int32>,因此在操作完成后 await 的评估会得出 Int32 值 (numRead)。 有关详细信息,请参阅异步返回类型 (C#)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public async Task ProcessReadAsync()
{
string filePath = @"temp2.txt";
if (File.Exists(filePath) == false)
{
Debug.WriteLine("file not found: " + filePath);
}
else
{
try
{
string text = await ReadTextAsync(filePath);
Debug.WriteLine(text);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
private async Task<string> ReadTextAsync(string filePath)
{
using (FileStream sourceStream = new FileStream(filePath,
FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: 4096, useAsync: true))
{
StringBuilder sb = new StringBuilder();
byte[] buffer = new byte[0x1000];
int numRead;
while ((numRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
string text = Encoding.Unicode.GetString(buffer, 0, numRead);
sb.Append(text);
}
return sb.ToString();
}
}
|
7.4. 并行异步 I/O¶
下面的示例通过编写 10 个文本文件来演示并行处理。 对于每个文件,WriteAsync 方法将返回一个任务,此任务随后将添加到任务列表中。 await Task.WhenAll(tasks); 语句将退出该方法,并在所有任务的文件处理完成时在此方法中继续。
该示例将在任务完成后关闭 finally 块中的所有 FileStream 实例。 如果每个 FileStream 均已在 using 语句中创建,则可能在任务完成前释放 FileStream。
请注意,性能提升几乎完全来自并行处理而不是异步处理。 异步的优点在于它不会占用多个线程,也不会占用用户界面线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | public async Task ProcessWriteMultAsync()
{
string folder = @"tempfolder\";
List<Task> tasks = new List<Task>();
List<FileStream> sourceStreams = new List<FileStream>();
try
{
for (int index = 1; index <= 10; index++)
{
string text = "In file " + index.ToString() + "\r\n";
string fileName = "thefile" + index.ToString("00") + ".txt";
string filePath = folder + fileName;
byte[] encodedText = Encoding.Unicode.GetBytes(text);
FileStream sourceStream = new FileStream(filePath,
FileMode.Append, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true);
Task theTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
sourceStreams.Add(sourceStream);
tasks.Add(theTask);
}
await Task.WhenAll(tasks);
}
finally
{
foreach (FileStream sourceStream in sourceStreams)
{
sourceStream.Close();
}
}
}
|