[ 공 부 ]/[ C# ]
19장. Task
HiStar__
2025. 1. 30. 16:11
[2025-01-30] - 게시글 최초 작성
1. Task / Task<TResult> / Parallel 란?
병렬 처리 : 하나의 작업을 여러 작업자가 나눠서 수행한 뒤 다시 하나의 결과로 만드는 것
비동기 처리 : 작업 A를 시작한 후 A 결과가 나올 때까지 B, C, D ... 를 수행하다가 작업 A가 끝나면 그 결과를 받아냄.
.NET 에서 지원하는 System.Threading.Tasks 네임스페이스의 클래스들,
async 한정자와 await 연산자
Task 등 클래스들도 내부적으로는 Thread를 이용하여 구현.
2. T a s k
2.1 System.Threading.Tasks.Task 클래스
동기 코드 : 메소드를 호출한 뒤에 이 메소드의 실행이 완전히 종료되어야만 다음 메소드를 호출 가능
비동기 코드 : 메소드를 호출한 뒤에 메소드의 종료를 기다리지 않고 다음 코드를 실행.
1. Action 대리자로 사용
2. Task.Run() 생성과 시작을 단번에 한다. ( 무명 함수 사용 )
[msdn] System.Threading.Tasks.Task
Task 클래스 (System.Threading.Tasks)
비동기 작업을 나타냅니다.
learn.microsoft.com
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace ThisIsCSharp
{
class MainApp
{
static void Main(string[] args)
{
string srcFile = args[0];
// Action 대리자
Action<object> FileCopyAction = (object state) =>
{
String[] paths = (String[])state;
File.Copy(paths[0], paths[1]);
Console.WriteLine($"Task:{Task.CurrentId}, ThreadID : {Thread.CurrentThread.ManagedThreadId}," +
$" {paths[0]} was copied to {paths[1]}");
};
Task t1 = new Task(FileCopyAction, new string[] { srcFile, srcFile + ".copy1" });
Task t2 = Task.Run(() =>
{
FileCopyAction(new string[] { srcFile, srcFile + ".copy2" });
});
t1.Start();
Task t3 = new Task(FileCopyAction, new string[] { srcFile, srcFile + ".copy3" });
// 동기 실행을 위한 메소드
t3.RunSynchronously();
t1.Wait();
t2.Wait();
t3.Wait();
}
}
}
2.2 코드의 비동기 실행 결과를 주는 Task<TResult> 클래스
1. 코드의 비동기 실행 결과를 얻을 수 있음
2. Func 대리자로 사용
3. Task 클래스를 사용 후 Wait() 를 호출하는 습관을 들이면 좋다!
Task<TResult> 클래스 (System.Threading.Tasks)
값을 반환할 수 있는 비동기 작업을 나타냅니다.
learn.microsoft.com
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ThisIsCSharp
{
class MainApp
{
static bool IsPrime(long number)
{
if (number < 2)
{
return false;
}
if(number % 2 == 0 && number != 2)
{
return false;
}
for(long i = 2; i < number; i++)
{
if(number % i == 0)
{
return false;
}
}
return true;
}
static void Main(string[] args)
{
long from = Convert.ToInt64("0");
long to = Convert.ToInt64("100000");
int taskCount = Convert.ToInt32("5");
// Func 대리자 인스턴스 생성
Func<object, List<long>> FindPrimeFunc = (objRange) =>
{
long[] range = (long[])objRange;
List<long> found = new List<long>();
for(long i = range[0]; i < range[1]; i++)
{
if(IsPrime(i))
{
found.Add(i);
}
}
return found;
};
// 범위 별로 세팅
Task<List<long>>[] tasks = new Task<List<long>>[taskCount];
long currentFrom = from;
long currentTo = to / tasks.Length;
for (int i = 0; i < tasks.Length; i++)
{
Console.WriteLine($"Task[{i}] : {currentFrom} ~ {currentTo}");
tasks[i] = new Task<List<long>>(FindPrimeFunc, new long[] { currentFrom, currentTo });
currentFrom = currentTo + 1;
if(i == tasks.Length - 2)
{
currentTo = to;
}
else
{
currentTo = currentTo + (to / tasks.Length);
}
}
Console.WriteLine("Please press Enter to Start ... ");
Console.ReadLine();
Console.WriteLine("Started ... ");
DateTime startTime = DateTime.Now;
// Task 시작
foreach(Task<List<long>> task in tasks)
{
task.Start();
}
List<long> total = new List<long>();
// 결과를 Task의 Result로 받아 올 수 있다.
foreach (Task<List<long>> task in tasks)
{
task.Wait();
total.AddRange(task.Result.ToArray());
}
DateTime endTime = DateTime.Now;
TimeSpan elapsed = endTime - startTime;
Console.WriteLine($"Prime Number Count Between {from} and {to} : {total.Count}");
Console.WriteLine($"Elapsed Time : {elapsed}");
}
}
}
2.3 Parallel 클래스
1. 손쉬운 병렬 처리를 지원. ( System.Threading.Tasks.Parallel 클래스 )
2. For(), Foreach() 등의 메소드를 제공 함으로써 Task<TResult> 보다 더 쉬운 구현을 제공한다.
3. Parallel.For() 메소드 : 등록한 메소드를 병렬로 호출 할 때, 몇 개의 스레드를 사용할지는 Parallel 클래스가
내부적으로 판단하여 최적화.
[msdn] System.Threading.Tasks.Parallel
Parallel 클래스 (System.Threading.Tasks)
병렬 루프 및 영역에 대한 지원을 제공합니다.
learn.microsoft.com
// Parallel 클래스 사용!
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ThisIsCSharp
{
class MainApp
{
static bool IsPrime(long number)
{
if (number < 2)
{
return false;
}
if(number % 2 == 0 && number != 2)
{
return false;
}
for(long i = 2; i < number; i++)
{
if(number % i == 0)
{
return false;
}
}
return true;
}
static void Main(string[] args)
{
long from = Convert.ToInt64("0");
long to = Convert.ToInt64("100000");
Console.WriteLine("Please press Enter to Start ... ");
Console.ReadLine();
Console.WriteLine("Started ... ");
DateTime startTime = DateTime.Now;
List<long> total = new List<long>();
Parallel.For(from, to, (long i) =>
{
if (IsPrime(i))
{
lock (total)
{
total.Add(i);
}
}
});
DateTime endTime = DateTime.Now;
TimeSpan elapsed = endTime - startTime;
Console.WriteLine($"Prime Number Count Between {from} and {to} : {total.Count}");
Console.WriteLine($"Elapsed Time : {elapsed}");
}
}
}
2.4 async 한정자 / await 연산자
C# ~ 5.0 : BeginInoke() / EndInvoke()
C# 5.0 ~ : async 한정자 / await 연산자2025-01-30] - 게시글 최초 작성
async 한정자
: 메소드, 이벤트 처리기, 태스크, 람다식 등을 수식함으로써 C# 컴파일러가 호출하는 코드를 만날 때
호출 결과를 기다리지 않고 바로 다음 코드로 이동하도록 실행 코드 생성.
* async로 한정하는 메소드의 경우, 반환 형식이 Task, Task<TResult>, void 형식이여야 한다.
"async로 한정한 Task 또는 Task<TResult>를 반환하는 메소드/Task/람다식은 await 연산자를 만나는 곳에서
호출자에게 제어를 돌려주며, await 연산자가 없는 경우 동기로 실행"
await 연산자
해당 함수가 끝날때까지 기다리면서 제어권은 호출자에게 넘겨준다.
Task.Delay()
: 함수가 하는 일은 인수로 입력받은 시간이 지나면 Task 객체를 반환
스레드를 블록시키지 않는다.
Thread.Sleep()
: 스레드 전체를 블록 시킨다.
using System;
using System.Threading.Tasks;
namespace ThisIsCSharp
{
class MainApp
{
// async 한정자로 함수 선언
async static private void MyMethodAsync(int count)
{
Console.WriteLine("C");
Console.WriteLine("D");
// 1. await
await Task.Run(async () =>
{
for (int i = 1; i <= count; i++)
{
Console.WriteLine($"{i}/{count} ...");
await Task.Delay(100); // Thread.Sleep()의 비동기 버전
}
});
Console.WriteLine("G");
Console.WriteLine("H");
}
static void Caller()
{
Console.WriteLine("A");
Console.WriteLine("B");
MyMethodAsync(3);
Console.WriteLine("E");
Console.WriteLine("F");
}
static void Main(string[] args)
{
Caller();
Console.ReadLine();
}
}
}
2.5 .NET이 제공하는 비동기 API
System.IO.Stream 클래스가 제공하는 ~ Async() 메소드들
System.IO.Stream
Read <-> ReadAsync
Write <-> WriteAsync
~ Async 함수의 경우 await 연산자 처리를 해야 한다.
using System;
using System.IO;
using System.Threading.Tasks;
namespace ThisIsCSharp
{
class MainApp
{
// 파일 복사 후 복사한 파일 용량 반환
static async Task<long> CopyAsync(string FromPath, string ToPath)
{
using (var fromStream = new FileStream(FromPath, FileMode.Open))
{
long totalCopied = 0;
using(var toStream = new FileStream(ToPath, FileMode.Create))
{
byte[] buffer = new byte[1024];
int nRead = 0;
while((nRead = await fromStream.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
await toStream.WriteAsync(buffer, 0, nRead);
totalCopied += nRead;
}
}
return totalCopied;
}
}
static async void DoCopy(string FromPath, string ToPath)
{
long totalCopied = await CopyAsync(FromPath, ToPath);
Console.WriteLine($"Copied Total {totalCopied} Bytes.");
}
static void Main(string[] args)
{
Console.WriteLine("Usage : AsyncFileIO <Source> <Destination>");
DoCopy("a.dat", "b.dat");
Console.ReadLine();
}
}
}
4. 추가 정보
I/O 바운드
: 컴퓨터가 어떤 작업을 할 때 CPU 보다는 입출력에 더 많은 시간을 사용하는 상황