对文件访问使用 Async (Visual Basic)

可以使用异步功能访问文件。 通过使用异步功能,可以调用异步方法,而无需使用回调或跨多个方法或 lambda 表达式拆分代码。 若要使同步代码异步,只需调用异步方法而不是同步方法,并向代码添加几个关键字。

你可以考虑以下将异步添加到文件访问调用中的原因:

  • 异步提高 UI 应用程序的响应性,因为启动操作的 UI 线程可以执行其他工作。 如果 UI 线程必须执行长时间(例如超过 50 毫秒)的代码,UI 可能会冻结,直到 I/O 完成,UI 线程可以再次处理键盘和鼠标输入和其他事件。

  • Asynchrony 通过减少线程需求来提高 ASP.NET 和其他基于服务器的应用程序的可伸缩性。 如果应用程序每个响应使用专用线程,并且同时处理一千个请求,则需要一千个线程。 异步作通常不需要在等待期间使用线程。 异步操作仅需在结束时短暂使用现有 I/O 完成线程。

  • 在当前条件下,文件访问作的延迟可能非常低,但将来的延迟可能会大大增加。 例如,可将文件移动到世界各地的服务器。

  • 使用异步功能的附加开销很小。

  • 异步任务可以轻松并行运行。

运行示例

若要运行本主题中的示例,可以创建 WPF 应用程序Windows 窗体应用程序 ,然后添加 按钮。 在按钮 Click 的事件中,添加对每个示例中第一个方法的调用。

在以下示例中,包括以下 Imports 语句。

Imports System  
Imports System.Collections.Generic  
Imports System.Diagnostics  
Imports System.IO  
Imports System.Text  
Imports System.Threading.Tasks  

使用 FileStream 类

本主题中的示例使用 FileStream 类,该类具有一个选项,导致异步 I/O 在操作系统级别发生。 使用此选项可以避免在许多情况下阻止 ThreadPool 线程。 若要启用此选项,请在构造函数调用中指定 useAsync=trueoptions=FileOptions.Asynchronous 参数。

如果通过指定文件路径直接打开 StreamReaderStreamWriter,则无法将此选项与这二者配合使用。 但是,如果为二者提供已由 Stream 类打开的 FileStream,则可以使用此选项。 请注意,即使线程池线程被阻止,UI 应用中的异步调用也更快,因为 UI 线程在等待期间不会被阻止。

编写文本

以下示例将文本写入文件。 在每个 await 语句中,该方法会立即退出。 文件输入/输出完成后,该方法将在 await 语句后面的语句处恢复执行。 请注意,异步修饰符位于使用 await 语句的方法的定义中。

Public Async Sub ProcessWrite()  
    Dim filePath = "temp2.txt"  
    Dim text = "Hello World" & ControlChars.CrLf  
  
    Await WriteTextAsync(filePath, text)  
End Sub  
  
Private Async Function WriteTextAsync(filePath As String, text As String) As Task  
    Dim encodedText As Byte() = Encoding.Unicode.GetBytes(text)  
  
    Using sourceStream As New FileStream(filePath,  
        FileMode.Append, FileAccess.Write, FileShare.None,  
        bufferSize:=4096, useAsync:=True)  
  
        Await sourceStream.WriteAsync(encodedText, 0, encodedText.Length)  
    End Using  
End Function  

原始例子包含陈述Await sourceStream.WriteAsync(encodedText, 0, encodedText.Length),这是以下两个陈述的缩合:

Dim theTask As Task = sourceStream.WriteAsync(encodedText, 0, encodedText.Length)  
Await theTask  

第一个语句返回任务并导致文件处理启动。 具有 await 的第二条语句将使方法立即退出并返回一个不同的任务。 文件处理稍后完成后,执行将返回到 await 后面的语句中。 有关详细信息,请参阅异步程序中的控制流(Visual Basic)。

读取文本

以下示例从文件读取文本。 在本例中,文本被缓冲后放置在一个 StringBuilder中。 与前一示例不同,await 的计算将生成一个值。 ReadAsync 方法返回 Task<Int32>,因此在操作完成后 await 的评估会得出 Int32 值 (numRead)。 有关详细信息,请参阅异步返回类型(Visual Basic)。

Public Async Sub ProcessRead()  
    Dim filePath = "temp2.txt"  
  
    If File.Exists(filePath) = False Then  
        Debug.WriteLine("file not found: " & filePath)  
    Else  
        Try  
            Dim text As String = Await ReadTextAsync(filePath)  
            Debug.WriteLine(text)  
        Catch ex As Exception  
            Debug.WriteLine(ex.Message)  
        End Try  
    End If  
End Sub  
  
Private Async Function ReadTextAsync(filePath As String) As Task(Of String)  
  
    Using sourceStream As New FileStream(filePath,  
        FileMode.Open, FileAccess.Read, FileShare.Read,  
        bufferSize:=4096, useAsync:=True)  
  
        Dim sb As New StringBuilder  
  
        Dim buffer As Byte() = New Byte(&H1000) {}  
        Dim numRead As Integer  
        numRead = Await sourceStream.ReadAsync(buffer, 0, buffer.Length)  
        While numRead <> 0  
            Dim text As String = Encoding.Unicode.GetString(buffer, 0, numRead)  
            sb.Append(text)  
  
            numRead = Await sourceStream.ReadAsync(buffer, 0, buffer.Length)  
        End While  
  
        Return sb.ToString  
    End Using  
End Function  

并行异步输入输出

以下示例通过编写 10 个文本文件来演示并行处理。 对于每个文件,该方法 WriteAsync 将返回一个任务,然后添加到任务列表。 文件处理完成所有任务后,Await Task.WhenAll(tasks) 语句将退出该方法,并在该方法中恢复执行。

该示例在任务完成后关闭块FileStream中的所有Finally实例。 如果每个 FileStream 是在 Imports 语句中创建的,FileStream 可能会在任务完成之前被销毁。

请注意,任何性能提升几乎完全来自并行处理,而不是异步处理。 异步的优点是它不会绑定多个线程,并且它不会绑定用户界面线程。

Public Async Sub ProcessWriteMult()  
    Dim folder = "tempfolder\"  
    Dim tasks As New List(Of Task)  
    Dim sourceStreams As New List(Of FileStream)  
  
    Try  
        For index = 1 To 10  
            Dim text = "In file " & index.ToString & ControlChars.CrLf  
  
            Dim fileName = "thefile" & index.ToString("00") & ".txt"  
            Dim filePath = folder & fileName  
  
            Dim encodedText As Byte() = Encoding.Unicode.GetBytes(text)  
  
            Dim sourceStream As New FileStream(filePath,  
                FileMode.Append, FileAccess.Write, FileShare.None,  
                bufferSize:=4096, useAsync:=True)  
  
            Dim theTask As Task = sourceStream.WriteAsync(encodedText, 0, encodedText.Length)  
            sourceStreams.Add(sourceStream)  
  
            tasks.Add(theTask)  
        Next  
  
        Await Task.WhenAll(tasks)  
    Finally  
        For Each sourceStream As FileStream In sourceStreams  
            sourceStream.Close()  
        Next  
    End Try  
End Sub  

使用 WriteAsyncReadAsync 方法时,可以指定一个 CancellationToken 用于在过程中取消操作。 有关详细信息,请参阅Fine-Tuning Your Async Application (Visual Basic)托管线程中的取消

另请参阅