방프리
24.05.29 C# 동시성 프로그래밍 (다양한 동시성 상황) 본문
1. 공유 리소스 초기화
공유하는 리소스를 생성할 때가 아닌 처음 사용 시 초기화를 할 때 Lazy<T>형식을 사용한다.
게으른 초기화라고도 하는데 팩토리 대리자와 함께 인스턴스를 생성한다.
static int _simpleValue;
static readonly Lazy<int> MySharedInteger = new Lazy<int>(() => _simpleValue++);
void UseSharedInteger()
{
int sharedValue = MySharedInteger.Value;
}
// 비동기 작업 시
static int _simpleValue;
static readonly Lazy<Task<int>> MySharedAsyncInteger =
new Lazy<Task<int>>(() => Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
return _simpleValue++;
}));
async Task GetSharedIntegerAsync()
{
int sharedValue = await MySharedInteger.Value;
}
Nito.AsyncEx의 AsyncLazy<T> 형식도 있으니 참고하는 것이 좋다.
2. System.Reactive의 지연 평가
옵저버블을 구독할 때마다 새로운 소스 옵저버블을 생성하고자 할 때 Observable.Defer 연산자를 통해 해결이 가능하다.
이 대리자는 옵저버블을 생성하는 팩토리 역할을 한다.
void SubscribeWithDefer()
{
void invokeServerObservable = Observable.Defer(
() => GetValueAsync().ToObservable());
invokeServerObservable.Subscribe(_ => {});
invokeServerObservable.Subscribe(_ => {});
Console.ReadKey();
}
async Task<int> GetValueAsync()
{
Console.WriteLine("Calling server...");
await Task.Delay(TimeSpan.FromSeconds(2));
Console.WriteLine("Returning result...");
return 13;
}
3. 비동기 데이터 바인딩
속성에 데이터를 바인딩하려면 결과를 동기적으로 즉시 반환해야 하는데 비동기적으로 결과값이 정해져 있다면 기본 결과값을 선반환 후 비동기 결과값으로 교체하는 방향으로 가야 한다. Nito.Mvvm.Async 라이브러리에서 NotifyTask 형식을 통해서 좀 더 간단하게 만들 수 있다.
class MyViewModel
{
public MyViewModel()
{
MyValue = NotifyTask.Create(CalculateMyValueAsync());
}
public NotifyTask<int> MyValue { get; private set; }
private async Task<int> CalculateMyValueAsync()
{
await Task.Delay(TimeSpan.FromSeconds(10));
return 13;
}
}
4. 암시적 상태
호출 스택의 다양한 위치에서 접근할 수 있는 상태 변수를 넣고 싶을 때 AsyncLocal<T> 형식을 사용하면 논리적 '컨텍스트' 상에 유지할 수 있는 개체를 만들어서 원하는 값을 저장할 수 있다. AsyncLocal<T>는 불변 데이터만 저장해야 하며 업데이트 시 기존 값을 덮어 써야 한다.
private static AsyncLocal<Grid> _operationId = new AsyncLocal<Guid>();
async task DoLongOperationAsync()
{
_operationId.Value = Guid.NewGuid();
await DoSomeStepOfOperationAsync();
}
async Task DoSomeStepOfOperationAsync()
{
await Task.Delay(100);
Trace.WriteLine("In operation: " + _operationId.Value);
}
AsyncLocal<T>를 숨기면 올바르게 업데이트가 가능하다.
internal sealed class AsyncLocalGuidStack
{
private readonly AsyncLocal<ImmutableStack<Guid>> _operationIds =
new AsyncLocal<ImmutableStack<Guid>>();
private ImmutableStack<Guid> Current =>
_operationIds.Value ?? ImmutableStack<Guid>.Empty;
//...
}
5. 동기 코드와 비동기 코드를 한 번에 구현
가능하면 비지니스 로직을 I/O 같은 부작용과 분리하는 포트 앤드 어댑터 설계로 구성하도록 노력해야 한다.
만약 레거시 코드라 해법을 찾지 못한다면 불리언 인수 처리법으로 동기 API와 비동기 API를 모두 노출하면서 모든 로직을 하나의 메서드에 담아두는 것도 하나의 방법이다.
private async Task<int> DelayAndReturnCore(bool sync)
{
int value = 100;
if (sync)
Thread.Sleep(value);
else
await Task.Delay(value);
return value;
}
// 비동기 API
public Task<int> DelayAndReturnAsync() =>
DelayAndReturnCore(sync: false);
// 동기 API
public int DelayAndReturn() =>
DelayAndReturnCore(sync: true).GetAwaiter().GetResult();
6. 데이터 흐름 메시를 사용한 철도지향 프로그래밍
데이터 흐름 메시는 처리에 실패하면 동작을 멈춘다. 이를 멈추지 않고 계속 동작을 유지하게 하고 싶으면 철도지향 프로그래밍을 참고하는 것이 좋다.
private static TransformBlock<Try<TInput>, Try<TOutput>>
RailwayTransform<TInput, TOutput>(Func<TInput, TOutput> func)
{
return new TransformBlock<Try<TInput>, Try<TOutput>>(t => t.Map(func));
}
7. 진행률 업데이트의 조절
진행률을 너무 빨리 보고받을 때 시간을 통해서 조금씩 조절하는 방법도 있다. System.Reactive에서 특별한 시간에 맞추어 조절할 수 있는 연산자가 있다.
public static class ObservableProgress
{
public static (IObservable<T>, IProgress<T>) CreateOrUi<T>(
TimeSpan? sampleInterval = null)
{
var (observable, progress) = Create<T>();
observable = observable
.Sample(sampleInterval ?? TimeSpan.FromMilliseconds(100))
.ObserveOn(SynchronizationContext.Current);
return (observable, progress);
}
}
private async void StartButton_Click(object sender, RoutedEventArgs e)
{
MyLabel.Content = "Starting...";
var (observable, progress) = ObservableProgress.CreateForUi<int>();
string result;
using (observable.Subscribe(value => MyLabel.Content = value))
result = await Task.Run(() => Solve(progress));
MyLabel.Content = $"Done! Result: {result}";
}
'C# > 동시성 처리' 카테고리의 다른 글
24.05.27 C# 동시성 프로그래밍 (스케줄링) (0) | 2024.05.27 |
---|---|
24.05.26 C# 동시성 프로그래밍 (동기화) (0) | 2024.05.26 |
24.05.24 C# 동시성 프로그래밍 (함수형 친화적 OOP) (0) | 2024.05.24 |
24.05.22 C# 동시성 프로그래밍 (취소) (0) | 2024.05.22 |
24.05.21 C# 동시성 프로그래밍 (컬렉션) (0) | 2024.05.21 |