Task는 C#에서 비동기 프로그래밍을 수행하기 위한 주요 클래스입니다.
비동기 프로그래밍을 통해 프로그램이 작업을 기다리는 동안 다른 작업을 수행할 수 있어 응답성이 높은 애플리케이션을 만들 수 있습니다.
Task의 기본 개념
- System.Threading.Tasks 네임스페이스에 포함되어 있습니다.
- Task는 비동기 작업을 나타내며, 비동기 코드의 실행 결과를 반환할 수 있습니다.
- Task를 통해 비동기 작업이 완료될 때까지 기다리거나, 완료 후에 후속 작업을 연결할 수 있습니다.
비동기 작업이란?
- 프로그램이 작업을 시작하면 그 작업이 백그라운드에서 실행되고, 프로그램은 다음 코드 줄로 넘어갑니다.
- 작업이 백그라운드에서 진행되는 동안 프로그램은 다른 작업을 수행하거나 다음 코드 줄로 넘어갈 수 있습니다.
Task의 기본 사용법
- Task 생성 및 실행
var myTask = Task.Run( ()=> //Task의 생성과 시작을 단번에 합니다. Task가 실행할 Action 대리자도 무명함수로 바꿉니다.
{
Thread.cleep(1000); //1초 대기
Console.WriteLine("Printed asynchronously.");
}
};
Console.WriteLine("Printed synchronously.");
myTask.Wati();
/*결과는
Printed synchronously.
Printed asynchronously. */
using System;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
// 컴퓨터가 빨래를 비동기적으로 시작함
Task doLaundry = Task.Run(() => {
Console.WriteLine("빨래를 시작합니다.");
Task.Delay(2000).Wait(); // 2초 동안 기다림 (빨래하는 중)
Console.WriteLine("빨래가 끝났습니다.");
});
// 컴퓨터가 설거지를 비동기적으로 시작함
Task doDishes = Task.Run(() => {
Console.WriteLine("설거지를 시작합니다.");
Task.Delay(1000).Wait(); // 1초 동안 기다림 (설거지하는 중)
Console.WriteLine("설거지가 끝났습니다.");
});
// 메인 프로그램은 다른 일을 할 수 있음
Console.WriteLine("나는 숙제를 합니다.");
// 모든 Task가 끝날 때까지 기다림
doLaundry.Wait();
doDishes.Wait();
Console.WriteLine("모든 집안일이 끝났습니다!");
}
}
<실행결과>
코드의 비동기 실행결과를 주는 Task<TResult>클래스
Task<TResult>란?
- Task<TResult>는 비동기 작업을 수행한 후 결과값을 반환하는 클래스입니다. 즉, 컴퓨터가 어떤 작업을 하고 나서 그 작업의 결과를 가져오는 비동기 작업이라고 생각하면 됩니다.
- TResult는 작업이 완료된 후 반환할 데이터의 타입을 의미합니다. 예를 들어, Task<int>는 작업이 끝나면 int 타입의 값을 반환한다는 뜻입니다.
Task<TResult> 사용법
using System;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
// 비동기 작업을 실행하고 그 결과값을 받는 Task<int> 사용
Task<int> resultTask = Task.Run(() => {
Console.WriteLine("숫자를 계산합니다...");
Task.Delay(2000).Wait(); // 2초 대기 (계산하는 중)
return 42; // 계산 결과를 반환
});
Console.WriteLine("다른 작업을 수행 중...");
// 결과를 기다렸다가 사용
int result = resultTask.Result;
Console.WriteLine($"계산된 결과는: {result}");
}
}
- Task<int> resultTask: 이 Task는 비동기 작업을 실행하고 int 타입의 결과값을 반환합니다.
- Task.Run(() => {...}): 비동기 작업을 시작하고 42라는 값을 반환하는 람다 함수가 실행됩니다.
- resultTask.Result: Task가 완료될 때까지 기다렸다가 42라는 값을 가져와서 출력해. 이 부분에서 프로그램은 잠깐 멈춰서 Task의 결과를 기다리지만, 그 외에는 다른 작업을 계속할 수 있습니다.
손쉬운 병렬 처리를 가능케 하는 Parallel 클래스
- Parallel 클래스는 여러 작업을 동시에 실행하기 위해 사용되는 C#의 기능이야. 이 클래스는 다중 코어 CPU를 효율적으로 활용해서 병렬 처리를 쉽게 구현할 수 있게 해줍니다.
- 예를 들어, 반복문을 쓸 때 한 번에 하나씩 실행하는 대신, 여러 작업을 병렬로 나눠서 동시에 실행하게 만듭니다.
언제 사용할까?
- 데이터나 연산이 많은 작업을 빠르게 처리하고 싶을 때.
- 병렬로 나눠서 실행할 수 있는 반복 작업이나 여러 개의 독립적인 작업을 할 때.
가장 많이 쓰이는 메서드:
- Parallel.For: for 루프처럼 작동하지만, 각 반복을 병렬로 실행합니다.
- Parallel.ForEach: 컬렉션의 각 항목을 병렬로 처리할 때 사용합니다.
Parallel.For
using System;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
// 1부터 10까지의 숫자를 병렬로 출력
Parallel.For(1, 11, i =>
{
Console.WriteLine($"숫자 {i} 처리 중... (스레드 ID: {Task.CurrentId})");
});
Console.WriteLine("모든 작업이 완료되었습니다!");
}
}
<실행결과>
- Parallel.For(1, 11, i => {...})는 1부터 10까지의 숫자를 병렬로 출력합니다. 즉, 각 숫자에 대해 코드 블록이 동시에 실행돼서 작업 속도가 더 빨라질 수 있습니다.
- Task.CurrentId는 현재 작업의 ID를 출력해서 병렬로 실행되는 각 작업이 다른 스레드에서 실행되고 있다는 걸 보여주줍니다.
Parallel.ForEach
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
// 씻어야 할 과일 목록
List<string> fruits = new List<string> { "사과", "바나나", "체리", "딸기", "포도" };
// 각 과일을 병렬로 처리
Parallel.ForEach(fruits, fruit =>
{
// 각 과일을 씻는 중
Console.WriteLine($"{fruit} 씻는 중... (스레드 ID: {Task.CurrentId})");
});
Console.WriteLine("모든 과일을 다 씻었습니다!");
}
}
<실행결과>
- Parallel.ForEach는 items 리스트의 각 항목을 동시에 처리합니다. 각 과일 이름을 병렬로 출력하니까 작업 속도가 더 빠를 수 있죠.
- 마찬가지로, Task.CurrentId를 통해 병렬 작업이 서로 다른 스레드에서 수행되는 걸 확인할 수 있었습니다.
async 한정자와 await연산자로 만드는 비동기 코드
async와 await란?
- async 한정자는 메서드나 함수가 비동기 작업을 할 수 있게 만들어주는 표시입니다. 이걸 붙이면 그 메서드 안에서 await를 사용할 수 있습니다.
- await 연산자는 비동기 작업이 완료될 때까지 잠깐 기다렸다가, 완료되면 이어서 다음 작업을 계속하도록 해줍니다. 하지만 기다리는 동안 메인 스레드는 멈추지 않고 다른 일을 할 수 있죠.
왜 사용하는 걸까요?
- 프로그램이 오래 걸리는 작업(예: 파일 읽기, 웹 요청, 데이터베이스 쿼리 등)을 할 때, 그 작업이 끝날 때까지 기다리면 프로그램이 멈춰서 다른 작업을 못 합니다. 하지만 async와 await를 사용하면 그 작업이 끝나기를 기다리는 동안에도 다른 일을 계속할 수 있죠.
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("파일 읽기를 시작합니다...");
// 비동기 파일 읽기 호출
string content = await ReadFileAsync("test.txt");
Console.WriteLine("파일 읽기 완료!");
Console.WriteLine($"파일 내용: {content.Substring(0, Math.Min(100, content.Length))}..."); // 첫 100자만 출력
}
static async Task<string> ReadFileAsync(string filePath)
{
using (StreamReader reader = new StreamReader(filePath))
{
// 비동기적으로 파일 내용을 읽음
string content = await reader.ReadToEndAsync();
return content; // 작업이 완료되면 내용을 반환
}
}
}
<실행 결과>
- ReadFileAsync 메서드: StreamReader를 사용해서 파일을 읽고, await reader.ReadToEndAsync()로 비동기적으로 파일 내용을 읽습니다.
- await의 역할: 파일 읽기가 끝날 때까지 기다리지만, 그 동안 프로그램은 다른 일을 할 수 있습니다.
- 결과: 파일 내용이 모두 읽히면 그제서야 content 변수에 값이 할당되고 출력됩니다.
.Net이 제공하는 비동기 API 맛보기
- 여러가지의 API가 있지만, 두가지만 다룬다고 합니다.
- ReadAsync와 WriteAsync는 비동기 입출력(I/O) 작업을 수행하는 API입니다.
- 이 메서드들은 파일이나 데이터 스트림에서 비동기적으로 읽고 쓰는 작업을 할 때 사용됩니다.
ReadAsync 메서드
- 역할: 파일이나 스트림에서 비동기적으로 데이터를 읽어오는 메서드입니다.
- 사용 예시: 큰 파일이나 네트워크 스트림에서 데이터를 읽을 때, 프로그램이 멈추지 않고 다른 작업을 할 수 있도록 해줍니다.
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
using (FileStream fs = new FileStream("example.txt", FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[1024]; // 읽어올 데이터를 담을 버퍼
int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length); // 비동기적으로 데이터 읽기
Console.WriteLine($"읽은 바이트 수: {bytesRead}");
Console.WriteLine($"내용: {System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead)}");
}
}
}
WriteAsync 메서드
- 역할: 파일이나 스트림에 비동기적으로 데이터를 쓰는 메서드입니다.
- 사용 예시: 파일이나 네트워크 스트림에 데이터를 쓸 때, 프로그램이 멈추지 않고 다른 작업을 할 수 있도록 해줍니다.
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
using (FileStream fs = new FileStream("output.txt", FileMode.Create, FileAccess.Write))
{
byte[] buffer = Encoding.UTF8.GetBytes("Hello, 비동기 쓰기!");
await fs.WriteAsync(buffer, 0, buffer.Length); // 비동기적으로 데이터 쓰기
Console.WriteLine("파일 쓰기 완료");
}
}
}
ReadAsync와 WriteAsync의 장점:
- 효율성: 비동기 작업 중에도 프로그램이 멈추지 않고 다른 일을 계속할 수 있어.
- 응답성: 특히 UI 프로그램에서 사용자 경험을 향상시켜. 예를 들어, 큰 파일을 읽는 동안 UI가 멈추지 않아.
- 리소스 최적화: 여러 비동기 작업을 할 때 스레드 풀을 효율적으로 사용해서 시스템 자원을 절약할 수 있어.
'C#' 카테고리의 다른 글
c#테트리스 게임만들기 (0) | 2024.11.04 |
---|---|
네트워크 프로그래밍 (0) | 2024.11.04 |
대리자와 이벤트 (0) | 2024.11.01 |
프로그래밍 (0) | 2024.11.01 |
배열과 컬렉션 그리고 인덱서 (0) | 2024.11.01 |
댓글