다음을 통해 공유


Async 및 Await를 사용한 비동기 프로그래밍(Visual Basic)

비동기 프로그래밍을 사용하여 성능 병목 상태를 방지하고 애플리케이션의 전반적인 응답성을 향상시킬 수 있습니다. 그러나 비동기 애플리케이션을 작성하는 기존의 기술은 복잡할 수 있으므로 작성, 디버그 및 유지 관리가 어려울 수 있습니다.

Visual Studio 2012는 Windows 런타임뿐만 아니라 .NET Framework 4.5 이상에서 비동기 지원을 활용하는 간소화된 비동기 프로그래밍 방식을 도입했습니다. 컴파일러는 개발자가 수행하는 데 사용한 어려운 작업을 수행하며 애플리케이션은 동기 코드와 유사한 논리 구조를 유지합니다. 결과적으로 비동기 프로그래밍의 모든 장점을 약간의 노력으로 얻을 수 있습니다.

이 항목에서는 비동기 프로그래밍을 사용하는 시기와 방법에 대한 개요를 제공하고 세부 정보 및 예제를 포함하는 지원 항목에 대한 링크를 포함합니다.

비동기 처리가 응답성을 향상시킵니다.

비동기는 애플리케이션이 웹에 액세스하는 경우와 같이 잠재적으로 차단되는 활동에 필수적입니다. 웹 리소스에 대한 액세스가 느리거나 지연되는 경우가 있습니다. 이러한 작업이 동기 프로세스 내에서 차단되면 전체 애플리케이션이 기다려야 합니다. 비동기 프로세스에서 애플리케이션은 잠재적으로 차단된 작업이 완료될 때까지 웹 리소스에 의존하지 않는 다른 작업을 계속할 수 있습니다.

다음 표에서는 비동기 프로그래밍이 응답성을 향상시키는 일반적인 영역을 보여 줍니다. .NET Framework 4.5 및 Windows 런타임의 나열된 API에는 비동기 프로그래밍을 지원하는 메서드가 포함되어 있습니다.

애플리케이션 영역 비동기 메서드를 포함하는 API 지원
웹 액세스 HttpClient, SyndicationClient
파일 작업 StorageFile, StreamWriter, StreamReaderXmlReader
이미지 작업 MediaCapture, , BitmapEncoderBitmapDecoder
WCF 프로그래밍 동기 및 비동기 작업

비동기 기능은 모든 UI 관련 작업이 일반적으로 하나의 스레드를 공유하므로 UI 스레드에 액세스하는 애플리케이션에 특히 유용합니다. 동기 애플리케이션에서 프로세스가 차단되면 모두 차단됩니다. 애플리케이션의 응답이 중지되고 대기 중일 때 실패했다고 결론을 내릴 수 있습니다.

비동기 메서드를 사용하는 경우 애플리케이션은 UI에 계속 응답합니다. 예를 들어 창의 크기를 조정하거나 최소화하거나 완료되기를 기다리지 않으려는 경우 애플리케이션을 닫을 수 있습니다.

비동기 기반 접근 방식은 비동기 작업을 디자인할 때 선택할 수 있는 옵션 목록에 자동 전송에 해당하는 값을 추가합니다. 즉, 기존 비동기 프로그래밍의 모든 이점을 얻을 수 있지만 개발자의 노력은 훨씬 적습니다.

비동기 메서드는 쓰기가 더 쉽습니다.

Visual Basic의 비동기Await 키워드는 비동기 프로그래밍의 핵심입니다. 이러한 두 키워드를 사용하면 .NET Framework 또는 Windows 런타임의 리소스를 사용하여 동기 메서드를 만드는 것만큼 쉽게 비동기 메서드를 만들 수 있습니다. 비동기 메서드는 AsyncAwait를 사용하여 정의하는 메서드를 가리킵니다.

다음 예제에서는 비동기 메서드를 보여줍니다. 코드의 거의 모든 항목이 사용자에게 완전히 친숙해 보일 것입니다. 주석은 비동기를 만들기 위해 추가하는 기능을 설명합니다.

이 항목의 끝부분에서 전체 WPF(Windows Presentation Foundation) 예제 파일을 찾을 수 있으며, "Async 및 Await를 사용한 비동기 프로그래밍"의 예제인 비동기 샘플에서 샘플을 다운로드할 수 있습니다.

' Three things to note about writing an Async Function:
'  - The function has an Async modifier.
'  - Its return type is Task or Task(Of T). (See "Return Types" section.)
'  - As a matter of convention, its name ends in "Async".
Async Function AccessTheWebAsync() As Task(Of Integer)
    Using client As New HttpClient()
        ' Call and await separately.
        '  - AccessTheWebAsync can do other things while GetStringAsync is also running.
        '  - getStringTask stores the task we get from the call to GetStringAsync.
        '  - Task(Of String) means it is a task which returns a String when it is done.
        Dim getStringTask As Task(Of String) =
            client.GetStringAsync("https://learn.microsoft.com/dotnet")
        ' You can do other work here that doesn't rely on the string from GetStringAsync.
        DoIndependentWork()
        ' The Await operator suspends AccessTheWebAsync.
        '  - AccessTheWebAsync does not continue until getStringTask is complete.
        '  - Meanwhile, control returns to the caller of AccessTheWebAsync.
        '  - Control resumes here when getStringTask is complete.
        '  - The Await operator then retrieves the String result from getStringTask.
        Dim urlContents As String = Await getStringTask
        ' The Return statement specifies an Integer result.
        ' A method which awaits AccessTheWebAsync receives the Length value.
        Return urlContents.Length

    End Using

End Function

호출 AccessTheWebAsync 과 완료 대기 사이에 수행할 수 있는 작업이 없는 경우 GetStringAsync 다음 단일 문에서 호출하고 대기하여 코드를 간소화할 수 있습니다.

Dim urlContents As String = Await client.GetStringAsync()

다음 특성은 이전 예제를 비동기 메서드로 만드는 항목을 요약합니다.

  • 메서드 서명에는 한정자가 Async 포함됩니다.

  • 규칙에 따라 비동기 메서드의 이름은 "Async" 접미사로 끝납니다.

  • 반환 형식은 다음 형식 중 하나입니다.

    • 메서드에 피연산자 형식이 TResult인 return 문이 있는 경우 Task(Of TResult)입니다.
    • Task 메서드에 return 문이 없거나 피연산자 없는 return 문이 있는 경우
    • 비동기 이벤트 처리기를 작성하는 경우 Sub입니다.

    자세한 내용은 이 항목의 뒷부분에 있는 "반환 형식 및 매개 변수"를 참조하세요.

  • 이 메서드는 일반적으로 대기 중인 비동기 작업이 완료될 때까지 메서드를 계속할 수 없는 지점을 표시하는 하나 이상의 await 식을 포함합니다. 그 동안 메서드가 일시 중단되고 컨트롤이 메서드의 호출자에게 반환됩니다. 이 항목의 다음 섹션에서는 일시 중단 지점에서 발생하는 작업을 보여 줍니다.

비동기 메서드에서는 제공된 키워드 및 형식을 사용하여 수행하려는 작업을 나타내고, 컴파일러는 컨트롤이 일시 중단된 메서드의 대기 지점으로 반환될 때 수행해야 하는 작업을 추적하는 등 나머지 작업을 수행합니다. 루프 및 예외 처리와 같은 일부 일상적인 프로세스는 기존 비동기 코드에서 처리하기 어려울 수 있습니다. 비동기 메서드에서는 동기 솔루션에서와 마찬가지로 이러한 요소를 작성하면 문제가 해결됩니다.

이전 버전의 .NET Framework의 비동기 기능에 대한 자세한 내용은 TPL 및 기존 .NET Framework 비동기 프로그래밍을 참조하세요.

비동기 메서드에서 발생하는 동작

비동기 프로그래밍에서 이해해야 할 가장 중요한 점은 제어 흐름이 메서드에서 메서드로 이동하는 방식입니다. 다음 다이어그램은 프로세스를 안내합니다.

비동기 프로그램 추적을 보여 주는 다이어그램

다이어그램의 숫자는 다음 단계에 해당합니다.

  1. 이벤트 처리기가 비동기 메서드를 AccessTheWebAsync 호출하고 대기합니다.

  2. AccessTheWebAsync 는 인스턴스를 HttpClient 만들고 비동기 메서드를 호출 GetStringAsync 하여 웹 사이트의 콘텐츠를 문자열로 다운로드합니다.

  3. 무언가가 GetStringAsync에서 발생하여 진행을 중단시킵니다. 웹 사이트가 다운로드되거나 다른 차단 작업이 수행되기를 기다려야 할 수 있습니다. 리소스 차단을 방지하기 위해 GetStringAsync는 제어를 해당 호출자 AccessTheWebAsync에게 넘깁니다.

    GetStringAsyncTResult가 문자열인 Task(Of TResult) 를 반환하고 AccessTheWebAsync 태스크를 변수에 getStringTask 할당합니다. 작업은 작업이 완료된 경우 실제 문자열 값을 생성하기 GetStringAsync위한 약정을 사용하여 호출에 대한 진행 중인 프로세스를 나타냅니다.

  4. 아직 getStringTask 대기하지 않았으므로 AccessTheWebAsync 최종 결과에 GetStringAsync의존하지 않는 다른 작업을 계속할 수 있습니다. 해당 작업은 동기 메서드 DoIndependentWork에 대한 호출로 표시됩니다.

  5. DoIndependentWork 는 해당 작업을 수행하고 호출자에게 반환하는 동기 메서드입니다.

  6. AccessTheWebAsync 에서 결과 getStringTask없이 수행할 수 있는 작업이 부족합니다. AccessTheWebAsync 은 다음으로 다운로드한 문자열의 길이를 계산하고 반환하려고 하지만 메서드가 문자열을 사용할 때까지 해당 값을 계산할 수 없습니다.

    따라서 AccessTheWebAsync await 연산자를 사용하여 진행을 일시 중지하고 호출한 AccessTheWebAsync 메서드에 제어권을 반환합니다. AccessTheWebAsync은 호출자에게 Task(Of Integer)을 반환합니다. 이 작업은 다운로드한 문자열의 길이인 정수 결과를 생성하겠다는 약속을 나타냅니다.

    비고

    GetStringAsync(따라서 getStringTask)가 AccessTheWebAsync가 대기하기 전에 완료되면, 컨트롤은 AccessTheWebAsync에서 유지됩니다. 호출된 비동기 프로세스(AccessTheWebAsync)가 이미 완료되고 AccessTheWebSync가 최종 결과를 기다릴 필요가 없는 경우 일시 중단 및 반환 getStringTask 비용이 낭비됩니다.

    호출자(이 예제의 이벤트 처리기) 내에서 처리 패턴이 계속됩니다. 호출자는 결과를 기다리기 전에 AccessTheWebAsync에 의존하지 않는 다른 작업을 수행할 수도 있고, 또는 즉시 그 결과를 기다릴 수도 있습니다. 이벤트 처리기가 대기 AccessTheWebAsync중이며 AccessTheWebAsync 대기 GetStringAsync중입니다.

  7. GetStringAsync 를 완료하고 문자열 결과를 생성합니다. 문자열 결과는 GetStringAsync 호출이 기대한 방식대로 반환되지 않습니다. (메서드가 이미 3단계에서 작업을 반환했음을 기억하세요.) 대신 문자열 결과는 메서드 getStringTask의 완료를 나타내는 태스크에 저장됩니다. await 연산자는 .에서 getStringTask결과를 검색합니다. 할당문은 검색된 결과를 urlContents에 할당합니다.

  8. 문자열 결과가 있는 경우 AccessTheWebAsync 메서드는 문자열의 길이를 계산할 수 있습니다. 그런 다음 작업 AccessTheWebAsync 도 완료되고 대기 이벤트 처리기를 다시 시작할 수 있습니다. 토픽의 끝에 있는 전체 예제에서 이벤트 처리기가 길이 결과의 값을 검색하고 출력하는 것을 확인할 수 있습니다.

비동기 프로그래밍을 접하는 경우 동기 동작과 비동기 동작의 차이점을 고려해 보세요. 동기 메서드는 작업이 완료되면 반환되지만(5단계) 비동기 메서드는 작업이 일시 중단될 때 작업 값을 반환합니다(3단계와 6단계). 비동기 메서드가 결국 작업을 완료하면 작업이 완료된 것으로 표시되고 결과가 있는 경우 태스크에 저장됩니다.

제어 흐름에 대한 자세한 내용은 비동기 프로그램의 제어 흐름(Visual Basic)을 참조하세요.

API 비동기 메서드

비동기 프로그래밍을 지원하는 메서드와 같은 GetStringAsync 메서드를 어디서 찾을 수 있는지 궁금할 수 있습니다. .NET Framework 4.5 이상에는 AsyncAwait와 함께 작동하는 많은 멤버들이 포함되어 있습니다. 멤버 이름 뒤에 붙은 "Async" 접미사와 반환 형식이 Task 또는 Task(Of TResult)인 경우 이러한 멤버를 인식할 수 있습니다. 예를 들어 클래스에는 System.IO.Stream 동기 메서드와 함께 , CopyToAsyncReadAsync .와 같은 WriteAsync메서드CopyToRead가 포함됩니다Write.

Windows 런타임에는 Windows 앱에서 AsyncAwait와 함께 사용할 수 있는 여러 메서드도 포함되어 있습니다. 자세한 내용 및 예제 메서드는 C# 또는 Visual Basic에서 비동기 API 호출, 비동기 프로그래밍(Windows 런타임 앱), 및 WhenAny: .NET Framework와 Windows 런타임 간 브리징을 참조하세요.

스레드

비동기 메서드는 비차단 작업입니다. Await 대기 중인 작업이 실행되는 동안 비동기 메서드의 식은 현재 스레드를 차단하지 않습니다. 대신 해당 식은 메서드의 남은 부분을 이어서 처리하도록 등록하고 비동기 메서드의 호출자에게 제어를 반환합니다.

AsyncAwait 키워드는 추가 스레드를 만들지 않습니다. 비동기 메서드는 자체 스레드에서 실행되지 않으므로 다중 스레딩이 필요하지 않습니다. 메서드는 현재 동기화 컨텍스트에서 실행되며 메서드가 활성 상태일 때만 스레드에서 시간을 사용합니다. CPU 바인딩된 작업을 백그라운드 스레드로 이동하는 데 사용할 Task.Run 수 있지만 백그라운드 스레드는 결과를 사용할 수 있길 기다리는 프로세스에 도움이 되지 않습니다.

비동기 프로그래밍에 대한 비동기 기반 접근 방식은 거의 모든 경우에 기존 접근 방식보다 좋습니다. 특히 이 방법은 코드가 더 간단하고 경합 상태를 방지할 필요가 없으므로 I/O 바운드 작업보다 BackgroundWorker 낫습니다. 비동기 프로그래밍을 Task.Run과 결합하여 사용하는 것이 BackgroundWorker보다 CPU 집약적인 작업에 더 적합합니다. 비동기 프로그래밍은 코드 실행의 조정 세부 정보를 스레드 풀로 넘기는 작업과 분리하므로 효율적입니다.

Async 및 Await

비동기 한정자를 사용하여 메서드가 비동기 메서드임을 지정하는 경우 다음 두 가지 기능을 사용하도록 설정합니다.

  • 표시된 비동기 메서드는 Await 를 사용하여 일시 중단 지점을 지정할 수 있습니다. await 연산자는 대기 중인 비동기 프로세스가 완료될 때까지 비동기 메서드가 해당 지점을 지나갈 수 없다는 것을 컴파일러에 알립니다. 그 동안 컨트롤은 비동기 메서드의 호출자로 돌아갑니다.

    식에서 Await 비동기 메서드의 일시 중단은 메서드 Finally 의 종료를 구성하지 않으며 블록이 실행되지 않습니다.

  • 표시된 비동기 메서드 자체는 해당 메서드를 호출하는 메서드에서 대기할 수 있습니다.

비동기 메서드는 일반적으로 하나 이상의 Await 연산자를 포함하지만, Await 식이 없다고 해서 컴파일러 오류가 발생하지는 않습니다. 비동기 메서드가 일시 중단 지점을 표시하는 연산자를 사용하지 Await 않는 경우 한정자에도 불구하고 Async 동기 메서드로 실행됩니다. 컴파일러는 이러한 메서드에 대한 경고를 실행합니다.

AsyncAwait은 컨텍스트 키워드입니다. 자세한 내용 및 예제는 다음 항목을 참조하세요.

반환 형식 및 매개 변수

.NET Framework 프로그래밍에서 비동기 메서드는 일반적으로 Task 또는 Task(Of TResult)를 반환합니다. 비동기 메서드 내에서 연산자는 다른 비동기 메서드 Await 에 대한 호출에서 반환되는 작업에 적용됩니다.

메서드에 형식 의 피연산자를 지정하는 Return 문이 포함된 경우 반환 형식으로 TResult를 지정합니다.

메서드에 return 문이 없거나 피연산자를 반환하지 않는 return 문이 있는 경우 반환 형식으로 사용합니다 Task .

다음 예제에서는 Task(Of TResult) 또는 Task을(를) 반환하는 메서드를 선언하고 호출하는 방법을 보여줍니다.

' Signature specifies Task(Of Integer)
Async Function TaskOfTResult_MethodAsync() As Task(Of Integer)

    Dim hours As Integer
    ' . . .
    ' Return statement specifies an integer result.
    Return hours
End Function

' Calls to TaskOfTResult_MethodAsync
Dim returnedTaskTResult As Task(Of Integer) = TaskOfTResult_MethodAsync()
Dim intResult As Integer = Await returnedTaskTResult
' or, in a single statement
Dim intResult As Integer = Await TaskOfTResult_MethodAsync()

' Signature specifies Task
Async Function Task_MethodAsync() As Task

    ' . . .
    ' The method has no return statement.
End Function

' Calls to Task_MethodAsync
Task returnedTask = Task_MethodAsync()
Await returnedTask
' or, in a single statement
Await Task_MethodAsync()

반환된 각 작업은 진행 중인 작업을 나타냅니다. 태스크는 비동기 프로세스의 상태에 대한 정보를 캡슐화하고, 결국 프로세스의 최종 결과 또는 프로세스가 성공하지 못하면 발생하는 예외를 캡슐화합니다.

비동기 메서드는 Sub 메서드일 수도 있습니다. 이 반환 형식은 주로 반환 형식이 필요한 이벤트 처리기를 정의하는 데 사용됩니다. 비동기 이벤트 처리기는 종종 비동기 프로그램의 시작점 역할을 합니다.

비동기 메서드가 프로시저인 경우, 기다릴 수 없으며 호출자는 메서드가 던지는 예외를 잡을 수 없습니다.

비동기 메서드는 ByRef 매개 변수를 선언할 수 없지만 메서드는 이러한 매개 변수가 있는 메서드를 호출할 수 있습니다.

자세한 내용 및 예제는 비동기 반환 형식(Visual Basic)을 참조하세요. 비동기 메서드에서 예외를 처리하는 자세한 방법에 대해서는 Try...Catch...Finally 문을 참조하세요.

Windows 런타임 프로그래밍의 비동기 API에는 작업과 유사한 다음 반환 형식 중 하나가 있습니다.

자세한 내용 및 예제는 C# 또는 Visual Basic에서 비동기 API 호출을 참조하세요.

명명 규칙

규칙에 따라 한정자가 있는 메서드 Async 의 이름에 "Async"를 추가합니다.

이벤트, 기본 클래스 또는 인터페이스 계약이 다른 이름을 제안하는 규칙을 무시할 수 있습니다. 예를 들어 다음과 같은 Button1_Click일반적인 이벤트 처리기의 이름을 바꾸면 안 됩니다.

관련 항목 및 샘플(Visual Studio)

제목 설명 예시
연습: Async 및 Await를 사용하여 웹에 액세스(Visual Basic) 동기 WPF 솔루션을 비동기 WPF 솔루션으로 변환하는 방법을 보여줍니다. 애플리케이션은 일련의 웹 사이트를 다운로드합니다. 비동기 샘플: Async 및 Await를 사용한 비동기 프로그래밍(Visual Basic)
방법: Task.WhenAll을 사용하여 비동기 예제 확장하기 (Visual Basic) 이전 연습에 Task.WhenAll을 추가합니다. 사용 WhenAll 은 모든 다운로드를 동시에 시작합니다.
방법: Async 및 Await를 사용하여 병렬로 여러 웹 요청 만들기(Visual Basic) 여러 작업을 동시에 시작하는 방법을 보여 줍니다. 비동기 샘플: 병렬로 여러 웹 요청 만들기
비동기 반환 형식(Visual Basic) 비동기 메서드가 반환할 수 있는 형식을 보여 줍니다. 각 형식이 적절한 경우를 설명합니다.
비동기 프로그램의 제어 흐름(Visual Basic) 비동기 프로그램에서 await 식의 연속을 통해 제어 흐름을 자세히 추적합니다. 비동기 샘플: 비동기 프로그램의 제어 흐름
비동기 애플리케이션Fine-Tuning(Visual Basic) 비동기 솔루션에 다음 기능을 추가하는 방법을 보여 줍니다.

- 비동기 작업 또는 작업 목록 취소(Visual Basic)
- 일정 기간 후 비동기 작업 취소(Visual Basic)
- 작업이 완료된 후 남은 비동기 작업 취소(Visual Basic)
- 여러 비동기 작업을 시작하고 완료되는 대로 처리하기 (Visual Basic)
비동기 샘플: 애플리케이션 미세 조정
비동기 앱에서 재진입 처리(Visual Basic) 활성 비동기 작업이 실행되는 동안 다시 시작되는 경우를 처리하는 방법을 보여 줍니다.
WhenAny: .NET Framework와 Windows 런타임을 연결하는 다리 Windows 런타임 메서드와 함께 사용할 WhenAny 수 있도록 .NET Framework의 작업 형식과 Windows 런타임의 IAsyncOperations 간에 연결하는 방법을 보여 줍니다. 비동기 샘플: .NET과 Windows 런타임 간 브리징(AsTask 및 WhenAny)
비동기 취소: .NET Framework와 Windows 런타임 사이 연결 Windows 런타임 메서드와 함께 사용할 CancellationTokenSource 수 있도록 .NET Framework의 작업 형식과 Windows 런타임의 IAsyncOperations 간에 연결하는 방법을 보여 줍니다. 비동기 샘플: .NET과 Windows 런타임 간의 연결(AsTask 및 취소)
파일 액세스에 비동기 사용(Visual Basic) 비동기 및 await를 사용하여 파일에 액세스할 때의 이점을 나열하고 보여 줍니다.
TAP(작업 기반 비동기 패턴) .NET Framework의 비동기 패턴에 대해 설명합니다. 패턴은 TaskTask(Of TResult) 형식들을 기반으로 합니다.

완성된 예시

다음 코드는 이 항목에서 설명하는 WPF(Windows Presentation Foundation) 애플리케이션의 MainWindow.xaml.vb 파일입니다. "Async 및 Await를 사용한 비동기 프로그래밍"의 예제인 비동기 샘플에서 샘플을 다운로드할 수 있습니다.


Imports System.Net.Http

' Example that demonstrates Asynchronous Programming with Async and Await.
' It uses HttpClient.GetStringAsync to download the contents of a website.
' Sample Output:
' Working . . . . . . .
'
' Length of the downloaded string: 39678.

Class MainWindow

    ' Mark the event handler with Async so you can use Await in it.
    Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs)

        ' Call and await immediately.
        ' StartButton_Click suspends until AccessTheWebAsync is done.
        Dim contentLength As Integer = Await AccessTheWebAsync()

        ResultsTextBox.Text &= $"{vbCrLf}Length of the downloaded string: {contentLength}.{vbCrLf}"

    End Sub


    ' Three things to note about writing an Async Function:
    '  - The function has an Async modifier.
    '  - Its return type is Task or Task(Of T). (See "Return Types" section.)
    '  - As a matter of convention, its name ends in "Async".
    Async Function AccessTheWebAsync() As Task(Of Integer)

        Using client As New HttpClient()

            ' Call and await separately.
            '  - AccessTheWebAsync can do other things while GetStringAsync is also running.
            '  - getStringTask stores the task we get from the call to GetStringAsync.
            '  - Task(Of String) means it is a task which returns a String when it is done.
            Dim getStringTask As Task(Of String) =
                client.GetStringAsync("https://learn.microsoft.com/dotnet")

            ' You can do other work here that doesn't rely on the string from GetStringAsync.
            DoIndependentWork()

            ' The Await operator suspends AccessTheWebAsync.
            '  - AccessTheWebAsync does not continue until getStringTask is complete.
            '  - Meanwhile, control returns to the caller of AccessTheWebAsync.
            '  - Control resumes here when getStringTask is complete.
            '  - The Await operator then retrieves the String result from getStringTask.
            Dim urlContents As String = Await getStringTask

            ' The Return statement specifies an Integer result.
            ' A method which awaits AccessTheWebAsync receives the Length value.
            Return urlContents.Length

        End Using

    End Function

    Sub DoIndependentWork()
        ResultsTextBox.Text &= $"Working . . . . . . .{vbCrLf}"
    End Sub

End Class

참고하십시오