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

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

请考虑出于以下原因将异步添加到文件访问调用:

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

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

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

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

  • 多个异步 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

多个异步 I/O 操作

以下示例启动多个异步写入操作。 运行时将这些操作排入队列,基础实现可能会使用操作系统(OS)异步 I/O 或线程池线程,具体取决于平台和配置,因此实际并发取决于 OS 和硬件。 对于每个文件,该方法 WriteAsync 将返回添加到任务列表的任务。 文件处理完成所有任务后,Await Task.WhenAll(tasks) 语句将退出该方法,并在该方法中恢复执行。

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

异步方法避免在 I/O 操作正在进行时阻止调用线程。 在许多情况下,吞吐量改进取决于 OS、硬件以及某些平台上的 .NET 运行时行为,例如线程池限制和计划。

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)托管线程中的取消

另请参阅