비동기 프로그래밍을 사용하여 성능 병목 상태를 방지하고 애플리케이션의 전반적인 응답성을 향상시킬 수 있습니다. 그러나 비동기 애플리케이션을 작성하는 기존의 기술은 복잡할 수 있으므로 작성, 디버그 및 유지 관리가 어려울 수 있습니다.
C#은 .NET 런타임에서 비동기 지원을 활용하는 간소화된 접근 방식인 비동기 프로그래밍을 지원합니다. 컴파일러는 개발자가 수행하는 데 사용한 어려운 작업을 수행하며 애플리케이션은 동기 코드와 유사한 논리 구조를 유지합니다. 결과적으로 비동기 프로그래밍의 모든 장점을 약간의 노력으로 얻을 수 있습니다.
이 항목에서는 비동기 프로그래밍을 사용하는 시기와 방법에 대한 개요를 제공하고 세부 정보 및 예제를 포함하는 지원 항목에 대한 링크를 포함합니다.
비동기 처리가 응답성을 향상시킵니다.
비동기는 웹 액세스와 같이 잠재적으로 차단되는 활동에 필수적입니다. 웹 리소스에 대한 액세스가 느리거나 지연되는 경우가 있습니다. 동기 프로세스에서 이러한 작업이 차단되면 전체 애플리케이션이 기다려야 합니다. 비동기 프로세스에서 애플리케이션은 잠재적으로 차단된 작업이 완료될 때까지 웹 리소스에 의존하지 않는 다른 작업을 계속할 수 있습니다.
다음 표에서는 비동기 프로그래밍이 응답성을 향상시키는 일반적인 영역을 보여 줍니다. .NET 및 Windows 런타임의 나열된 API에는 비동기 프로그래밍을 지원하는 메서드가 포함되어 있습니다.
애플리케이션 영역 | 비동기 메서드를 사용한 .NET 형식 | 비동기 메서드를 사용하는 Windows 런타임 형식 |
---|---|---|
웹 액세스 | HttpClient | Windows.Web.Http.HttpClient SyndicationClient |
파일 작업 | JsonSerializer StreamReader StreamWriter XmlReader XmlWriter |
StorageFile |
이미지 작업 | MediaCapture BitmapEncoder BitmapDecoder |
|
WCF 프로그래밍 | 동기 및 비동기 작업 |
비동기 기능은 모든 UI 관련 작업이 일반적으로 하나의 스레드를 공유하므로 UI 스레드에 액세스하는 애플리케이션에 특히 유용합니다. 동기 애플리케이션에서 프로세스가 차단되면 모두 차단됩니다. 애플리케이션의 응답이 중지되고 대기 중일 때 실패했다고 결론을 내릴 수 있습니다.
비동기 메서드를 사용하는 경우 애플리케이션은 UI에 계속 응답합니다. 예를 들어 창의 크기를 조정하거나 최소화하거나 완료되기를 기다리지 않으려는 경우 애플리케이션을 닫을 수 있습니다.
비동기 기반 접근 방식은 비동기 작업을 디자인할 때 선택할 수 있는 옵션 목록에 자동 전송에 해당하는 값을 추가합니다. 즉, 기존 비동기 프로그래밍의 모든 이점을 얻을 수 있지만 개발자의 노력은 훨씬 적습니다.
비동기 메서드는 쓰기가 쉽습니다.
C# 의 비동기 및 대기 키워드는 비동기 프로그래밍의 핵심입니다. 이러한 두 키워드를 사용하면 .NET Framework, .NET Core 또는 Windows 런타임의 리소스를 사용하여 동기 메서드를 만드는 것만큼 쉽게 비동기 메서드를 만들 수 있습니다. 키워드를 사용하여 정의하는 비동기 메서드를 async
비동기 메서드라고 합니다.
다음 예제에서는 비동기 메서드를 보여줍니다. 코드의 거의 모든 항목이 친숙해 보일 것입니다.
C#의 비동기 프로그래밍에서 사용되는 async 및 await와 관련하여, 다운로드 가능한 완전한 Windows Presentation Foundation (WPF) 예제를 에서 찾을 수 있습니다.
public async Task<int> GetUrlContentLengthAsync()
{
using var client = new HttpClient();
Task<string> getStringTask =
client.GetStringAsync("https://learn.microsoft.com/dotnet");
DoIndependentWork();
string contents = await getStringTask;
return contents.Length;
}
void DoIndependentWork()
{
Console.WriteLine("Working...");
}
이전 샘플에서 몇 가지 사례를 알아볼 수 있습니다. 메서드 시그니처로 시작합니다. 여기에는 한정자가 async
포함됩니다. 반환 형식은 (자세한 옵션은 "반환 형식" 섹션 참조)입니다 Task<int>
. 메서드 이름은 .로 Async
끝납니다. 메서드 본문에서 GetStringAsync
가 Task<string>
를 반환합니다. 즉, await
을(를) 수행할 때 string
(contents
)을(를) 얻게 됩니다. 작업을 기다리기 전에 GetStringAsync
로부터 string
에 의존하지 않는 작업을 수행할 수 있습니다.
await
연산자에 주의하세요. 이것을 일시 중단합니다.GetUrlContentLengthAsync
-
GetUrlContentLengthAsync
완료될 때까지getStringTask
계속할 수 없습니다. - 한편 제어는 호출자에게로 반환됩니다
GetUrlContentLengthAsync
. - 컨트롤이 완료되면 여기에서
getStringTask
다시 시작됩니다. - 그런 다음
await
연산자는getStringTask
에서string
결과를 검색합니다.
return 문은 정수 결과를 지정합니다. 대기 중인 GetUrlContentLengthAsync
모든 메서드는 길이 값을 검색합니다.
호출 GetStringAsync
과 완료 대기 사이에 수행할 수 있는 작업이 없는 경우 GetUrlContentLengthAsync
다음 단일 문에서 호출하고 대기하여 코드를 간소화할 수 있습니다.
string contents = await client.GetStringAsync("https://learn.microsoft.com/dotnet");
다음 특성은 이전 예제를 비동기 메서드로 만드는 항목을 요약합니다.
메서드 서명에는 한정자가
async
포함됩니다.규칙에 따라 비동기 메서드의 이름은 "Async" 접미사로 끝납니다.
반환 형식은 다음 형식 중 하나입니다.
-
Task<TResult> 메서드에 피연산자의 형식
TResult
이 있는 return 문이 있는 경우 . - Task 메서드에 return 문이 없거나 피연산자 없는 return 문이 있는 경우
-
void
비동기 이벤트 처리기를 작성하는 경우 - 다른 형식으로
GetAwaiter
메서드가 있는 경우.
자세한 내용은 반환 형식 및 매개 변수 섹션을 참조하세요.
-
Task<TResult> 메서드에 피연산자의 형식
이 메서드는 일반적으로 대기 중인 비동기 작업이 완료될 때까지 메서드를 계속할 수 없는 지점을 표시하는 하나
await
이상의 식을 포함합니다. 그 동안 메서드가 일시 중단되고 컨트롤이 메서드의 호출자에게 반환됩니다. 이 항목의 다음 섹션에서는 일시 중단 지점에서 발생하는 작업을 보여 줍니다.
비동기 메서드에서는 제공된 키워드 및 형식을 사용하여 수행하려는 작업을 나타내고, 컴파일러는 컨트롤이 일시 중단된 메서드의 대기 지점으로 반환될 때 수행해야 하는 작업을 추적하는 등 나머지 작업을 수행합니다. 루프 및 예외 처리와 같은 일부 일상적인 프로세스는 기존 비동기 코드에서 처리하기 어려울 수 있습니다. 비동기 메서드에서는 동기 솔루션에서와 마찬가지로 이러한 요소를 작성하면 문제가 해결됩니다.
이전 버전의 .NET Framework의 비동기 기능에 대한 자세한 내용은 TPL 및 기존 .NET Framework 비동기 프로그래밍을 참조하세요.
비동기 메서드에서 발생하는 동작
비동기 프로그래밍에서 이해해야 할 가장 중요한 점은 제어 흐름이 메서드에서 메서드로 이동하는 방식입니다. 다음 다이어그램은 프로세스를 안내합니다.
다이어그램의 숫자는 호출 메서드가 비동기 메서드를 호출할 때 시작되는 다음 단계에 해당합니다.
호출 메서드는 비동기 메서드를
GetUrlContentLengthAsync
호출하고 대기합니다.GetUrlContentLengthAsync
는 인스턴스를 HttpClient 만들고 비동기 메서드를 호출 GetStringAsync 하여 웹 사이트의 콘텐츠를 문자열로 다운로드합니다.무언가가
GetStringAsync
에서 발생하여 진행을 중단시킵니다. 웹 사이트가 다운로드되거나 다른 차단 작업이 수행되기를 기다려야 할 수 있습니다. 리소스 차단을 방지하기 위해GetStringAsync
는 제어를 해당 호출자GetUrlContentLengthAsync
에게 넘깁니다.GetStringAsync
Task<TResult>는 문자열인TResult
를 반환하며,GetUrlContentLengthAsync
태스크를getStringTask
변수에 할당합니다. 작업은 작업이 완료된 경우 실제 문자열 값을 생성하기GetStringAsync
위한 약정을 사용하여 호출에 대한 진행 중인 프로세스를 나타냅니다.아직
GetUrlContentLengthAsync
대기하지 않았으므로getStringTask
최종 결과에GetStringAsync
의존하지 않는 다른 작업을 계속할 수 있습니다. 해당 작업은 동기 메서드DoIndependentWork
에 대한 호출로 표시됩니다.DoIndependentWork
는 해당 작업을 수행하고 호출자에게 반환하는 동기 메서드입니다.GetUrlContentLengthAsync
에서 결과getStringTask
없이 수행할 수 있는 작업이 부족합니다.GetUrlContentLengthAsync
은 다음으로 다운로드한 문자열의 길이를 계산하고 반환하려고 하지만 메서드가 문자열을 사용할 때까지 해당 값을 계산할 수 없습니다.따라서
GetUrlContentLengthAsync
await 연산자를 사용하여 진행을 일시 중지하고 호출한GetUrlContentLengthAsync
메서드에 제어권을 반환합니다.GetUrlContentLengthAsync
은 호출자에게Task<int>
을 반환합니다. 이 작업은 다운로드한 문자열의 길이인 정수 결과를 생성하겠다는 약속을 나타냅니다.비고
GetStringAsync
가 완료되면 (따라서getStringTask
도 완료되면)GetUrlContentLengthAsync
가 이를 대기하기 전에, 제어는GetUrlContentLengthAsync
에 남아 있게 됩니다. 호출된 비동기 프로세스getStringTask
가 이미 완료되었고GetUrlContentLengthAsync
가 최종 결과를 기다릴 필요가 없는 경우,GetUrlContentLengthAsync
을(를) 일시 중단하고 다시 호출하는 데 드는 비용이 낭비됩니다.호출 메서드 내에서 처리 패턴이 계속됩니다. 호출자는 결과를 기다리기 전에
GetUrlContentLengthAsync
에 의존하지 않는 다른 작업을 수행할 수도 있고, 또는 즉시 그 결과를 기다릴 수도 있습니다. 호출 메서드가 대기GetUrlContentLengthAsync
중이며GetUrlContentLengthAsync
대기GetStringAsync
중입니다.GetStringAsync
를 완료하고 문자열 결과를 생성합니다. 문자열 결과는GetStringAsync
호출이 기대한 방식대로 반환되지 않습니다. (메서드가 이미 3단계에서 작업을 반환했음을 기억하세요.) 대신 문자열 결과는 메서드getStringTask
의 완료를 나타내는 태스크에 저장됩니다. await 연산자는 .에서getStringTask
결과를 검색합니다. 할당문은 검색된 결과를contents
에 할당합니다.문자열 결과가 있는 경우
GetUrlContentLengthAsync
메서드는 문자열의 길이를 계산할 수 있습니다. 그런 다음 작업GetUrlContentLengthAsync
도 완료되고 대기 이벤트 처리기를 다시 시작할 수 있습니다. 토픽의 끝에 있는 전체 예제에서 이벤트 처리기가 길이 결과의 값을 검색하고 출력하는 것을 확인할 수 있습니다. 비동기 프로그래밍을 접하는 경우 동기 동작과 비동기 동작의 차이점을 고려해 보세요. 동기 메서드는 작업이 완료되면 반환되지만(5단계) 비동기 메서드는 작업이 일시 중단될 때 작업 값을 반환합니다(3단계와 6단계). 비동기 메서드가 결국 작업을 완료하면 작업이 완료된 것으로 표시되고 결과가 있는 경우 태스크에 저장됩니다.
API 비동기 메서드
비동기 프로그래밍을 지원하는 메서드와 같은 GetStringAsync
메서드를 어디서 찾을 수 있는지 궁금할 수 있습니다. .NET Framework 4.5 이상 및 .NET Core에는 async
및 await
와 함께 작동하는 많은 멤버가 포함되어 있습니다. 이들은 멤버 이름에 추가된 "Async" 접미사와 Task 또는 Task<TResult>의 반환 형식으로 인식할 수 있습니다. 예를 들어 클래스에는 System.IO.Stream
동기 메서드와 함께 , ReadAsync및 WriteAsync .와 같은 CopyToAsync메서드CopyToRead가 포함됩니다Write.
Windows 런타임에는 Windows 앱에서 async
및 await
와 함께 사용할 수 있는 여러 메서드도 포함되어 있습니다. 자세한 내용은 UWP 개발을 위한 스레딩 및 비동기 프로그래밍 , 비동기 프로그래밍(Windows 스토어 앱) 및 빠른 시작: 이전 버전의 Windows 런타임을 사용하는 경우 C# 또는 Visual Basic에서 비동기 API 호출 을 참조하세요.
스레드
비동기 메서드는 비차단 작업입니다.
await
대기 중인 작업이 실행되는 동안 비동기 메서드의 식은 현재 스레드를 차단하지 않습니다. 대신 해당 식은 메서드의 남은 부분을 이어서 처리하도록 등록하고 비동기 메서드의 호출자에게 제어를 반환합니다.
async
및 await
키워드는 추가 스레드를 만들지 않습니다. 비동기 메서드는 자체 스레드에서 실행되지 않으므로 다중 스레딩이 필요하지 않습니다. 메서드는 현재 동기화 컨텍스트에서 실행되며 메서드가 활성 상태일 때만 스레드에서 시간을 사용합니다. CPU 바인딩된 작업을 백그라운드 스레드로 이동하는 데 사용할 Task.Run 수 있지만 백그라운드 스레드는 결과를 사용할 수 있길 기다리는 프로세스에 도움이 되지 않습니다.
비동기 프로그래밍에 대한 비동기 기반 접근 방식은 거의 모든 경우에 기존 접근 방식보다 좋습니다. 특히 이 방법은 코드가 더 간단하고 경합 상태를 방지할 필요가 없으므로 I/O 바인딩된 작업의 클래스보다 BackgroundWorker 낫습니다. 메서드 Task.Run와 결합하여 사용하면 비동기 프로그래밍은 코드 실행의 조정 세부 사항을 스레드 풀로 전송하는 작업 Task.Run
와 분리되므로 CPU 바운드 작업에서는 BackgroundWorker보다 더 효과적입니다.
async 및 await
비동기 한정자를 사용하여 메서드를 비동기 메서드로 지정하면 다음 두 가지 기능을 사용하도록 설정합니다.
표시된 비동기 메서드는 await 를 사용하여 일시 중단 지점을 지정할 수 있습니다.
await
연산자는 대기 중인 비동기 프로세스가 완료될 때까지 비동기 메서드가 해당 지점을 지나갈 수 없다고 컴파일러에 지시합니다. 그 동안 컨트롤은 비동기 메서드의 호출자로 돌아갑니다.식에서
await
비동기 메서드의 일시 중단은 메서드finally
의 종료를 구성하지 않으며 블록이 실행되지 않습니다.표시된 비동기 메서드 자체는 해당 메서드를 호출하는 메서드에서 대기할 수 있습니다.
비동기 메서드는 일반적으로 하나 이상의 await
연산자를 포함하지만, await
식이 없다고 해서 컴파일러 오류가 발생하지는 않습니다. 비동기 메서드가 일시 중단 지점을 표시하는 연산자를 사용하지 await
않는 경우 한정자에도 불구하고 async
동기 메서드로 실행됩니다. 컴파일러는 이러한 메서드에 대한 경고를 실행합니다.
async
및 await
은 컨텍스트 키워드입니다. 자세한 내용 및 예제는 다음 항목을 참조하세요.
- 비동기
- 기다리다
반환 형식 및 매개 변수
비동기 메서드는 일반적으로 Task 또는 Task<TResult>를 반환합니다. 비동기 메서드 내에서 연산자는 다른 비동기 메서드 await
에 대한 호출에서 반환되는 작업에 적용됩니다.
메서드에 형식 TResult
의 피연산자를 지정하는 return
문이 포함된 경우, 반환 형식을 Task<TResult>으로 지정합니다.
메서드에 return 문이 없거나 피연산자를 반환하지 않는 return 문이 있는 경우 반환 형식으로 사용합니다 Task .
형식에 메서드가 포함되어 있는 경우 다른 반환 형식을 지정할 수도 있습니다 GetAwaiter
.
ValueTask<TResult> 는 이러한 형식의 예입니다.
System.Threading.Tasks.Extension NuGet 패키지에서 사용할 수 있습니다.
다음 예제는 Task<TResult> 또는 Task을 반환하는 메서드를 선언하고 호출하는 방법을 보여줍니다.
async Task<int> GetTaskOfTResultAsync()
{
int hours = 0;
await Task.Delay(0);
return hours;
}
Task<int> returnedTaskTResult = GetTaskOfTResultAsync();
int intResult = await returnedTaskTResult;
// Single line
// int intResult = await GetTaskOfTResultAsync();
async Task GetTaskAsync()
{
await Task.Delay(0);
// No return statement needed
}
Task returnedTask = GetTaskAsync();
await returnedTask;
// Single line
await GetTaskAsync();
반환된 각 작업은 진행 중인 작업을 나타냅니다. 태스크는 비동기 프로세스의 상태에 대한 정보를 캡슐화하고, 결국 프로세스의 최종 결과 또는 프로세스가 성공하지 못하면 발생하는 예외를 캡슐화합니다.
비동기 메서드는 반환 형식을 void
가질 수도 있습니다. 이 반환 형식은 주로 void
반환 형식이 필요한 이벤트 처리기를 정의하는 데 사용됩니다. 비동기 이벤트 처리기는 종종 비동기 프로그램의 시작점 역할을 합니다.
반환 형식으로 void
를 갖는 비동기 메서드는 대기할 수 없으며, void를 반환하는 메서드의 호출자는 해당 메서드가 throw하는 예외를 catch할 수 없습니다.
비동기 메서드는 in, ref 또는 out 매개 변수를 선언할 수 없지만, 이러한 매개 변수가 있는 메서드를 호출할 수 있습니다. 마찬가지로 비동기 메서드는 참조 반환 값을 사용하여 메서드를 호출할 수 있지만 참조로 값을 반환할 수 없습니다.
자세한 내용 및 예제는 비동기 반환 형식(C#)을 참조하세요.
Windows 런타임 프로그래밍의 비동기 API에는 작업과 유사한 다음 반환 형식 중 하나가 있습니다.
- IAsyncOperation<TResult>에 해당하는 Task<TResult>
- IAsyncAction에 해당하는 Task
- IAsyncActionWithProgress<TProgress>
- IAsyncOperationWithProgress<TResult,TProgress>
명명 규칙
규칙에 따라 일반적으로 대기 가능한 형식(예: Task
, , Task<T>
ValueTask
ValueTask<T>
)을 반환하는 메서드에는 "Async"로 끝나는 이름이 있어야 합니다. 비동기 작업을 시작하지만 대기 가능한 형식을 반환하지 않는 메서드에는 "Async"로 끝나는 이름이 없어야 하지만 "Begin", "Start" 또는 다른 동사로 시작하여 이 메서드가 작업 결과를 반환하거나 throw하지 않음을 시사할 수 있습니다.
이벤트, 기본 클래스 또는 인터페이스 계약이 다른 이름을 제안하는 규칙을 무시할 수 있습니다. 예를 들어 다음과 같은 OnButtonClick
일반적인 이벤트 처리기의 이름을 바꾸면 안 됩니다.
관련 문서(Visual Studio)
제목 | 설명 |
---|---|
async 및 await를 사용하여 여러 웹 요청을 병렬로 만드는 방법(C#) | 여러 작업을 동시에 시작하는 방법을 보여 줍니다. |
비동기 반환 형식(C#) | 비동기 메서드가 반환할 수 있는 형식을 보여 줍니다. 각 형식이 적절한 경우를 설명합니다. |
취소 토큰을 신호 메커니즘으로 사용하여 작업을 취소합니다. | 비동기 솔루션에 다음 기능을 추가하는 방법을 보여 줍니다. - 작업 목록 취소(C#) - 일정 기간 후 작업 취소(C#) - 비동기 작업을 완료될 때마다 처리하기(C#) |
파일 액세스에 비동기 사용(C#) | 비동기 및 await를 사용하여 파일에 액세스할 때의 이점을 나열하고 보여 줍니다. |
TAP(작업 기반 비동기 패턴) | 비동기 패턴을 설명하며, 패턴은 Task 형식 및 Task<TResult> 형식을 기반으로 합니다. |
채널 9의 비동기 비디오 | 비동기 프로그래밍에 대한 다양한 비디오에 대한 링크를 제공합니다. |
참고하십시오
.NET