다음을 통해 공유


비동기 프로그램의 제어 흐름(C# 및 Visual Basic)

Async 및 Await 키워드를 사용하여 비동기 프로그램을 보다 쉽게 작성하고 유지할 수 있습니다. 그러나 프로그램 작동 방식을 잘 이해하지 못한다면 예기치 않은 결과가 나타날 수 있습니다. 이 항목에서는 한 메서드에서 다른 메서드로 컨트롤이 이동하는 시간과 매번 전송되는 정보를 표시하는 간단한 비동기 프로그램을 통해 컨트롤 흐름을 추적합니다.

참고

Async 및 Await 키워드는 Visual Studio 2012에서 도입되었습니다.

일반적으로 비동기 코드를 포함하는 메서드는 Async(Visual Basic) 또는 비동기(C#) 한정자로 표시합니다. 비동기 한정자로 표시된 메서드에서 대기(Visual Basic) 또는 대기(C#) 연산자를 사용하여 호출된 비동기 프로세스를 완료할 때까지 메서드가 일시 중지 상태로 대기할 위치를 지정할 수 있습니다. 자세한 내용은 Async 및 Await를 사용한 비동기 프로그래밍(C# 및 Visual Basic)을 참조하십시오.

다음 예제에서는 비동기 메서드를 사용하여 지정된 웹 사이트의 콘텐츠를 문자열로 다운로드하고 문자열의 길이를 표시합니다. 예제에는 다음 두 개의 메서드가 포함되어 있습니다.

  • startButton_Click - AccessTheWebAsync를 호출하고 결과를 표시합니다.

  • 웹 사이트의 콘텐츠를 문자열로 다운로드하고 문자열의 길이를 반환하는 AccessTheWebAsync입니다. AccessTheWebAsync는 비동기 HttpClient 메서드인 GetStringAsync(String)를 사용하여 콘텐츠를 다운로드합니다.

번호가 매겨진 디스플레이 줄이 프로그램 전반에 걸쳐 전략적 시점에 표시되어 프로그램이 실행되는 방법을 이해할 수 있으며 표시된 각 지점에서 수행된 작업을 설명합니다. 표시 줄은 "1" -"6"으로 레이블이 지정됩니다. 레이블은 프로그램이 이러한 코드 줄에 도달하는 순서를 나타냅니다.

다음 코드는 프로그램 개요를 보여 줍니다.

Class MainWindow

    Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs) Handles StartButton.Click

        ' ONE
        Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()

        ' FOUR
        Dim contentLength As Integer = Await getLengthTask

        ' SIX
        ResultsTextBox.Text &=
            String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, contentLength)

    End Sub


    Async Function AccessTheWebAsync() As Task(Of Integer)

        ' TWO
        Dim client As HttpClient = New HttpClient() 
        Dim getStringTask As Task(Of String) = 
            client.GetStringAsync("https://msdn.microsoft.com")

        ' THREE
        Dim urlContents As String = Await getStringTask

        ' FIVE
        Return urlContents.Length
    End Function

End Class
public partial class MainWindow : Window
{
    // . . .
    private async void startButton_Click(object sender, RoutedEventArgs e)
    {
        // ONE
        Task<int> getLengthTask = AccessTheWebAsync();

        // FOUR
        int contentLength = await getLengthTask;

        // SIX
        resultsTextBox.Text +=
            String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
    }


    async Task<int> AccessTheWebAsync()
    {
        // TWO
        HttpClient client = new HttpClient();
        Task<string> getStringTask =
            client.GetStringAsync("https://msdn.microsoft.com");

        // THREE                 
        string urlContents = await getStringTask;

        // FIVE
        return urlContents.Length;
    }
}

"1"부터 "6"까지 레이블이 표시된 위치는 현재 프로그램 상태에 대한 정보를 표시합니다. 다음 출력이 생성됩니다.

ONE:   Entering startButton_Click.
           Calling AccessTheWebAsync.

TWO:   Entering AccessTheWebAsync.
           Calling HttpClient.GetStringAsync.

THREE: Back in AccessTheWebAsync.
           Task getStringTask is started.
           About to await getStringTask & return a Task<int> to startButton_Click.

FOUR:  Back in startButton_Click.
           Task getLengthTask is started.
           About to await getLengthTask -- no caller to return to.

FIVE:  Back in AccessTheWebAsync.
           Task getStringTask is complete.
           Processing the return statement.
           Exiting from AccessTheWebAsync.

SIX:   Back in startButton_Click.
           Task getLengthTask is finished.
           Result from AccessTheWebAsync is stored in contentLength.
           About to display contentLength and exit.

Length of the downloaded string: 33946.

프로그램 설정

이 항목이 사용하는 코드를 MSDN에서 다운로드하거나 자체적으로 직접 빌드할 수 있습니다.

참고

예제를 실행하려면 Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012, Visual Studio Express 2013 for Windows 또는 .NET Framework 4.5나 4.5.1이 컴퓨터에 설치되어 있어야 합니다.

프로그램 다운로드

Async 샘플: Async 프로그램의 제어 흐름에서 이 항목에 대한 응용 프로그램을 다운로드할 수 있습니다. 다음 단계는 프로그램을 열고 실행합니다.

  1. 다운로드한 파일의 압축을 푼 다음 Visual Studio를 시작합니다.

  2. 메뉴 모음에서 파일, 열기, 프로젝트/솔루션을 선택합니다.

  3. 비압축된 샘플 코드를 보관하는 폴더로 이동하고 솔루션 파일(.sln)을 연 다음 F5 키를 사용하여 프로젝트를 빌드하고 실행합니다.

프로그램 직접 빌드

다음 WPF(Windows Presentation Foundation) 프로젝트는 이 항목에 대한 코드 예제를 포함합니다.

프로젝트를 실행하려면 다음 단계를 수행합니다.

  1. Visual Studio를 시작합니다.

  2. 메뉴 모음에서 파일, 새로 만들기, 프로젝트를 선택합니다.

    새 프로젝트 대화 상자가 열립니다.

  3. 설치된 템플릿에서 Visual Basic 또는 **Visual C#**을 선택한 다음 프로젝트 형식 목록에서 WPF 응용 프로그램을 선택합니다.

  4. 프로젝트 이름으로 AsyncTracer를 입력한 다음 확인 단추를 선택합니다.

    솔루션 탐색기에 새 프로젝트가 나타납니다.

  5. Visual Studio 코드 편집기에서 MainWindow.xaml 탭을 선택합니다.

    탭이 표시되지 않는 경우 솔루션 검색기에서 MainPage.xaml의 바로 가기 메뉴를 연 다음코드 보기를 선택합니다.

  6. MainWindow.xaml의 XAML 보기에서 코드를 다음 코드로 바꿉니다.

    <Window
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="MainWindow"
        Title="Control Flow Trace" Height="350" Width="525">
        <Grid>
            <Button x:Name="StartButton" Content="Start" HorizontalAlignment="Left" Margin="221,10,0,0" VerticalAlignment="Top" Width="75"/>
            <TextBox x:Name="ResultsTextBox" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Bottom" Width="510" Height="265" FontFamily="Lucida Console" FontSize="10" VerticalScrollBarVisibility="Visible" d:LayoutOverrides="HorizontalMargin"/>
    
        </Grid>
    </Window>
    
    <Window
            xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="AsyncTracer.MainWindow"
            Title="Control Flow Trace" Height="350" Width="592">
        <Grid>
            <Button x:Name="startButton" Content="Start&#xa;" HorizontalAlignment="Left" Margin="250,10,0,0" VerticalAlignment="Top" Width="75" Height="24"  Click="startButton_Click" d:LayoutOverrides="GridBox"/>
            <TextBox x:Name="resultsTextBox" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Bottom" Width="576" Height="265" FontFamily="Lucida Console" FontSize="10" VerticalScrollBarVisibility="Visible" Grid.ColumnSpan="3"/>
        </Grid>
    </Window>
    

    텍스트 상자 및 단추가 포함된 간단한 창이 MainWindow.xaml의 디자인 보기에 나타납니다.

  7. System.Net.Http에 대해 참조를 추가합니다.

  8. 솔루션 탐색기에서 MainWindow.xaml.vb 또는 MainWindow.xaml.cs의 바로 가기 메뉴를 열고 코드 보기를 선택합니다.

  9. MainWindow.xaml.vb 또는 MainWindow.xaml.cs에서 코드를 다음 코드로 바꿉니다.

    ' Add an Imports statement and a reference for System.Net.Http. 
    Imports System.Net.Http
    
    Class MainWindow
    
        Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs) Handles StartButton.Click
    
            ' The display lines in the example lead you through the control shifts.
            ResultsTextBox.Text &= "ONE:   Entering StartButton_Click." & vbCrLf &
                "           Calling AccessTheWebAsync." & vbCrLf
    
            Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
    
            ResultsTextBox.Text &= vbCrLf & "FOUR:  Back in StartButton_Click." & vbCrLf &
                "           Task getLengthTask is started." & vbCrLf &
                "           About to await getLengthTask -- no caller to return to." & vbCrLf
    
            Dim contentLength As Integer = Await getLengthTask
    
            ResultsTextBox.Text &= vbCrLf & "SIX:   Back in StartButton_Click." & vbCrLf &
                "           Task getLengthTask is finished." & vbCrLf &
                "           Result from AccessTheWebAsync is stored in contentLength." & vbCrLf &
                "           About to display contentLength and exit." & vbCrLf
    
            ResultsTextBox.Text &=
                String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, contentLength)
        End Sub
    
    
        Async Function AccessTheWebAsync() As Task(Of Integer)
    
            ResultsTextBox.Text &= vbCrLf & "TWO:   Entering AccessTheWebAsync." 
    
            ' Declare an HttpClient object. 
            Dim client As HttpClient = New HttpClient()
    
            ResultsTextBox.Text &= vbCrLf & "           Calling HttpClient.GetStringAsync." & vbCrLf
    
            ' GetStringAsync returns a Task(Of String).  
            Dim getStringTask As Task(Of String) = client.GetStringAsync("https://msdn.microsoft.com")
    
            ResultsTextBox.Text &= vbCrLf & "THREE: Back in AccessTheWebAsync." & vbCrLf &
                "           Task getStringTask is started." 
    
            ' AccessTheWebAsync can continue to work until getStringTask is awaited.
    
            ResultsTextBox.Text &=
                vbCrLf & "           About to await getStringTask & return a Task(Of Integer) to StartButton_Click." & vbCrLf
    
            ' Retrieve the website contents when task is complete. 
            Dim urlContents As String = Await getStringTask
    
            ResultsTextBox.Text &= vbCrLf & "FIVE:  Back in AccessTheWebAsync." &
                vbCrLf & "           Task getStringTask is complete." &
                vbCrLf & "           Processing the return statement." &
                vbCrLf & "           Exiting from AccessTheWebAsync." & vbCrLf
    
            Return urlContents.Length
        End Function 
    
    End Class
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    // Add a using directive and a reference for System.Net.Http; 
    using System.Net.Http;
    
    namespace AsyncTracer
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private async void startButton_Click(object sender, RoutedEventArgs e)
            {
                // The display lines in the example lead you through the control shifts.
                resultsTextBox.Text += "ONE:   Entering startButton_Click.\r\n" +
                    "           Calling AccessTheWebAsync.\r\n";
    
                Task<int> getLengthTask = AccessTheWebAsync();
    
                resultsTextBox.Text += "\r\nFOUR:  Back in startButton_Click.\r\n" +
                    "           Task getLengthTask is started.\r\n" +
                    "           About to await getLengthTask -- no caller to return to.\r\n";
    
                int contentLength = await getLengthTask;
    
                resultsTextBox.Text += "\r\nSIX:   Back in startButton_Click.\r\n" +
                    "           Task getLengthTask is finished.\r\n" +
                    "           Result from AccessTheWebAsync is stored in contentLength.\r\n" +
                    "           About to display contentLength and exit.\r\n";
    
                resultsTextBox.Text +=
                    String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
            }
    
    
            async Task<int> AccessTheWebAsync()
            {
                resultsTextBox.Text += "\r\nTWO:   Entering AccessTheWebAsync.";
    
                // Declare an HttpClient object.
                HttpClient client = new HttpClient();
    
                resultsTextBox.Text += "\r\n           Calling HttpClient.GetStringAsync.\r\n";
    
                // GetStringAsync returns a Task<string>. 
                Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");
    
                resultsTextBox.Text += "\r\nTHREE: Back in AccessTheWebAsync.\r\n" +
                    "           Task getStringTask is started.";
    
                // AccessTheWebAsync can continue to work until getStringTask is awaited.
    
                resultsTextBox.Text +=
                    "\r\n           About to await getStringTask and return a Task<int> to startButton_Click.\r\n";
    
                // Retrieve the website contents when task is complete. 
                string urlContents = await getStringTask;
    
                resultsTextBox.Text += "\r\nFIVE:  Back in AccessTheWebAsync." +
                    "\r\n           Task getStringTask is complete." +
                    "\r\n           Processing the return statement." +
                    "\r\n           Exiting from AccessTheWebAsync.\r\n";
    
                return urlContents.Length;
            }
        }
    }
    
  10. F5 키를 선택하여 프로그램을 실행한 다음 시작 단추를 선택합니다.

    다음과 같은 출력이 나타나야 합니다.

    ONE:   Entering startButton_Click.
               Calling AccessTheWebAsync.
    
    TWO:   Entering AccessTheWebAsync.
               Calling HttpClient.GetStringAsync.
    
    THREE: Back in AccessTheWebAsync.
               Task getStringTask is started.
               About to await getStringTask & return a Task<int> to startButton_Click.
    
    FOUR:  Back in startButton_Click.
               Task getLengthTask is started.
               About to await getLengthTask -- no caller to return to.
    
    FIVE:  Back in AccessTheWebAsync.
               Task getStringTask is complete.
               Processing the return statement.
               Exiting from AccessTheWebAsync.
    
    SIX:   Back in startButton_Click.
               Task getLengthTask is finished.
               Result from AccessTheWebAsync is stored in contentLength.
               About to display contentLength and exit.
    
    Length of the downloaded string: 33946.
    

프로그램 추적

1, 2단계

표시되는 처음 두 줄은 startButton_Click이 AccessTheWebAsync를 호출하고 AccessTheWebAsync가 비동기 HttpClient 메서드 GetStringAsync(String)를 호출할 때 경로를 추적합니다. 다음 이미지는 메서드에서 메서드까지의 호출을 간략하게 설명합니다.

1, 2단계

AccessTheWebAsync 및 client.GetStringAsync 의 반환 형식은 Task입니다. AccessTheWebAsync의 경우 TResult는 정수입니다. GetStringAsync의 경우 TResult는 문자열입니다. 비동기 메서드 반환 형식에 대한 자세한 내용은 비동기 반환 형식(C# 및 Visual Basic)을 참조하십시오.

컨트롤이 호출자로 되돌아갈 경우 작업 반환 비동기 메서드는 작업 인스턴스를 반환합니다. Await 또는 await 연산자가 호출된 메서드에서 발생하거나 호출된 메서드가 끝나게 되면 컨트롤이 비동기 메서드에서 호출자로 반환됩니다. "3" -"6"으로 레이블이 지정된 표시 줄은 이 프로세스의 일부를 추적합니다.

3단계

AccessTheWebAsync에서는 대상 웹페이지의 내용을 다운로드하기 위해 비동기 메서드 GetStringAsync(String)을 호출합니다. client.GetStringAsync가 반환되는 경우 client.GetStringAsync에서 AccessTheWebAsync로의 반환을 제어합니다.

client.GetStringAsync 메서드는 AccessTheWebAsync의 getStringTask 변수에 할당되는 문자열 작업을 반환합니다. 예제 프로그램의 다음 줄은 client.GetStringAsync으로의 호출과 할당을 보여 줍니다.

Dim getStringTask As Task(Of String) = client.GetStringAsync("https://msdn.microsoft.com")
Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");

client.GetStringAsync에 의해 작업을 약속으로 간주하여 궁극적으로 실제 문자열을 생성할 수 있습니다. 한편, AccessTheWebAsync이 client.GetStringAsync에서 약속한 문자열에 의존하지 않는 작업을 해야 할 경우 client.GetStringAsync이 대기하는 동안 해당 작업을 계속할 수 있습니다. 예제에서 출력 다음에 "3"으로 레이블이 지정된 줄은 독립적인 작업을 수행하는 기회를 나타냅니다.

THREE: Back in AccessTheWebAsync.
           Task getStringTask is started.
           About to await getStringTask & return a Task<int> to startButton_Click.

다음 문은 getStringTask가 완료될 때까지 AccessTheWebAsync의 진행을 일시 중단시킵니다.

Dim urlContents As String = Await getStringTask
string urlContents = await getStringTask;

다음 이미지는 client.GetStringAsync에서 getStringTask에 대한 할당까지와 getStringTask 작성에서 await 연산자 적용까지의 컨트롤 흐름을 보여 줍니다.

3단계

await 식은 client.GetStringAsync가 반환될 때까지 AccessTheWebAsync를 일시 중지합니다. 한편, 컨트롤이 AccessTheWebAsync, startButton_Click의 호출자로 반환됩니다.

참고

일반적으로 비동기 메서드에 대한 호출을 즉시 기다립니다.예를 들어 다음 할당 중 1개로 getStringTask을 생성한 다음 이를 대기하는 이전 코드를 바꿀 수 있습니다.

  • Visual Basic: Dim urlContents As String = Await client.GetStringAsync("https://msdn.microsoft.com")

  • C#: string urlContents = await client.GetStringAsync("https://msdn.microsoft.com");

이 항목에서 프로그램을 통한 제어의 흐름을 표시하는 출력 줄을 고려하여 await 연산자는 나중에 적용됩니다.

4단계

AccessTheWebAsync의 선언된 반환 형식은 Task(Of Integer)(Visual Basic) 및 Task<int>(C#)입니다. 따라서 AccessTheWebAsync가 일시 중단되면 startButton_Click에 정수의 새 작업이 반환됩니다. 반환된 작업이 getStringTask가 아니라는 것을 이해해야 합니다. 반환된 작업은 일시 중단된 메서드 AccessTheWebAsync에서 수행하도록 남아 있는 항목을 나타내는 정수의 새 작업입니다. 작업은 작업이 완료되면 정수를 생성하기 위한 AccessTheWebAsync로부터의 약속입니다.

다음 문은 getLengthTask 변수에 이 작업을 할당합니다.

Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
Task<int> getLengthTask = AccessTheWebAsync();

AccessTheWebAsync에서와 마찬가지로 startButton_Click는 작업이 대기할 때까지 비동기 작업(getLengthTask)의 결과에 따라 달라지지 않는 작업을 계속할 수 있습니다. 다음 출력 줄은 해당 작업을 나타냅니다.

FOUR:  Back in startButton_Click.
           Task getLengthTask is started.
           About to await getLengthTask -- no caller to return to.

startButton_Click의 진행 과정은 getLengthTask이 대기할 때 일시 중단됩니다. 다음 대입문은 startButton_Click을 AccessTheWebAsync이 완료될 때까지 일시 중단시킵니다.

Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;

다음 그림에서 화살표는 AccessTheWebAsync에 있는 await 식에서 getLengthTask에 대한 값의 할당까지 그리고 getLengthTask이 대기할 때까지 startButton_Click에서 정상적인 처리로 이어지는 제어의 흐름을 보여줍니다.

4단계

5단계

AccessTheWebAsync에서 처리 중인 client.GetStringAsync가 완료되어 일시 중단에서 해제되고 await 문을 계속할 수 있음을 신호로 알리는 경우 다음 출력 줄은 다시 처리하기 시작함을 나타냅니다.

FIVE:  Back in AccessTheWebAsync.
           Task getStringTask is complete.
           Processing the return statement.
           Exiting from AccessTheWebAsync.

urlContents.Length, Return 문의 피연산자는 AccessTheWebAsync가 반환하는 작업에 저장됩니다. await 식은 startButton_Click의 getLengthTask에서 값을 검색합니다.

다음 이미지는 client.GetStringAsync(및 getStringTask)가 완료된 후 제어가 반환되었음을 보여 줍니다.

5단계

AccessTheWebAsync가 실행 완료되고, 컨트롤은 완료를 대기하는 startButton_Click으로 반환됩니다.

6단계

AccessTheWebAsync가 완료되었다고 신호를 보내면 startButton_Async에서 await 문을 지나 처리를 계속할 수 있습니다. 실제로 이 프로그램에는 더 이상 수행할 작업이 없습니다.

다음 출력 줄은 startButton_Async에서 다시 처리하기 시작함을 나타냅니다.

SIX:   Back in startButton_Click.
           Task getLengthTask is finished.
           Result from AccessTheWebAsync is stored in contentLength.
           About to display contentLength and exit.

대기 식은 AccessTheWebAsync에서 반환 문의 피연산자인 getLengthTask 정수 값에서 검색합니다. 다음 문은 contentLength 변수에 해당 값을 할당합니다.

Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;

다음 이미지는 AccessTheWebAsync에서 startButton_Click으로 제어가 반환되었음을 보여 줍니다.

6단계

참고 항목

작업

연습: Async 및 Await를 사용하여 웹에 액세스(C# 및 Visual Basic)

연습: Async 메서드에 디버거 사용

개념

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

비동기 반환 형식(C# 및 Visual Basic)

기타 리소스

Async 샘플: Async 프로그램의 제어 흐름(C# 및 Visual Basic)