방프리
23.11.05 C# 동시성 프로그래밍 (기초 기능) 본문
1. 일정 시간동안 정지
- 간단한 딜레이 타임을 주는 정도의 기능
- 시나리오 코드 및 간단한 테스트 용도로 사용한다. (이외 실제 기능 혹은 배포에서는 들어가면 좋지 않은 코드)
async Task<T> DelayResult<T>(T result, TimeSpan delay)
{
await Task.Delay(delay);
return result;
}
// delay를 통한 soft timeout 기능 구현
async Task<string> DownloadStringWithTimeout(HttpClient client, string url)
{
using var tokenSource= new CancellationTokenSource(TimeSpan.FromSecond(3));
Task<string> downloadTask = client.GetStringAsync(uri);
Task timeoutTask = Task.Delay(Timeout.InfiniteTimeSpan, tokenSource.Token);
var completedTask = await Task.WhenAny(downloadTask, timeoutTask);
if (completedTask == timeoutTask)
{
return null;
}
return await downloadTask;
}
2. 완료한 작업 반환
- 비동기 시그니처를 사용해서 동기 메서드를 구현
- 단위 테스트 진행 시 간단한 스텁(stub: 빈 메서드) 혹은 목 (mock: 가짜객체)가 필요할 때
interface IMyAsyncInterface
{
Task<int> GetValueAsync();
}
class MySynchronousImplementation : IMyAsyncInterface
{
public Task<int> GetValueAsync()
{
return Task.FromResult(13);
}
}
//간단한 완료형 Task를 사용하고 싶다면
class MySynchronousImplementation : IMyAsyncInterface
{
//...
public Task DoSomething()
{
return Task.CompletedTask;
}
}
//Exception을 반환하고 싶다면
Task<T> NotImplementedAsync<T>()
{
return Task.FromException<T>(new NotImplementedException());
}
//Task 중간에 작업을 취소해야한다면 적극적으로 CancellationToken을 활용하자
Task<int> GetValueAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
reuturn Task.FromCancled<int>(cancellationToken);
}
return Task.FromResult(13);
}
3. 진행 상황 보고
- UI 쓰레드와 별개로 다른 로딩 Task의 진행상황 정보를 알려고 할 때
async Task MyMethodAsync(IProgress<double> progress = null)
{
bool done = false;
double percentComplete = 0;
while (done == false)
{
//...
progress?.Report(percentComplete);
}
}
// call like this
async Task CallMyMethodAsync()
{
var progress = new Progress<double>();
progress.ProgressChanged += (sender, args) =>
{
//...
};
await MyMethodAsync(progress);
}
4. 모든 작업 완료를 대기
- Task.WhenAll로 해결 하지만 수많은 작업 (Task)이 몰렸을 때에는 좋은 방법은 아님
- 이외에 ForEachAsync 등의 기능을 활용하는 방안도 있음
async Task<string> DownloadAllAsync(HttpClient client,
IENumerable<string> urls)
{
var downloads = urls.Select(url => client.GetStringAsync(url));
Task<string>[] downloadTasks = downloads.ToArray();
string[] htmlPages = await Task.WhenAll(downloadTasks);
return string.Concat(htmlPages);
}
5. 여러 작업 중 하나의 완료를 대기
- 하나의 작업만 완료해도 바로 대기를 중지한다.
- 단, 나머지 작업들(Task)는 버려진다.
async Task<int> FirstRespondingUrlAsync(HttpClient client,
string urlA, string urlB)
{
Task<byte[]> downloadTaskA = client.GetByteArrayAsync(urlA);
Task<byte[]> downloadTaskB = client.GetByteArrayAsync(urlB);
var completedTask =
await Task.WhenAny(downloadTaskA, downloadTaskB);
var data = await completedTask;
return data.Length;
}
6. 작업이 완료될 때마다 처리
- 여러 개의 작업을 동시에 실행하되, 완료되는 순차적으로 처리
- 순차처리가 목적이라면 잠금(lock)을 고려해볼 것
async Task<int> DelayAndReturnAsync(int value)
{
await Task.Delay(TimeSpan.FromSecond(value));
return value;
}
async Task AwaitAndProcessAsync(Task<int> task)
{
var result = await task;
Trace.WriteLine(result);
}
async Task ProcessTasksAsync()
{
Task<int> taskA = DelayAndReturnAsync(2);
Task<int> taskB = DelayAndReturnAsync(3);
Task<int> taskC = DelayAndReturnAsync(1);
Task<int>[] tasks = new[] { taskA, taskB, taskC };
IEnumerable<Task> taskQuery =
from t in tasks select AwaitAndProcessAsync(t);
Task[] processingTasks = taskQuery.ToArray();
await Task.WhenAll(processingTasks);
}
7. 연속 작업용 컨텍스트 회피
- async, await는 항상 같은 컨텍스트에서 실행된다. 이로 인해 UI 컨텍스트에서 수많은 async 메서드가 실행될 경우
성능 이슈가 있을 수 있다.
async Task ResumeOnContextAsync()
{
await Task.Delay(TimeSpan.FromSecond(1));
}
async Task ResumeWithoutContextAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
}
8. async Task 메서드의 예외 처리
async Task ThrowExceptionAsync()
{
await Task.Delay(TimeSpan.FromSecond(1));
throw new InvalidOperationException("Test");
}
async Task TestAsync()
{
try
{
await ThrowExceptionAsync();
}
catch (InvalidOperationException e)
{
//...
}
}
9. async void 메서드의 예외 처리
- async void 사용을 최대한 하지 말 것
10. ValueTask 생성
- 메서드 내의 동작이 비동기 반환형식보다 동기 반환 케이스가 더 많을 때 사용
- ValueTask 또한 내부는 Task와 동일한 구조이지만 Task에 비해 좀 더 오버헤드가 발생
- 결국 실제 성능향상이 이루어지는지 보려면 각 케이스 별로 프로파일링을 진행한 후 선택
'C# > 동시성 처리' 카테고리의 다른 글
23.11.19 C# 동시성 프로그래밍 (테스트) (0) | 2023.11.19 |
---|---|
23.11.12 C# 동시성 프로그래밍 (Reactive) (1) | 2023.11.12 |
23.11.11 C# 동시성 프로그래밍 (TPL) (0) | 2023.11.11 |
23.11.08 C# 동시성 프로그래밍 (병렬 처리) (0) | 2023.11.08 |
23.11.07 C# 동시성 프로그래밍 (비동기 스트림) (0) | 2023.11.07 |