방프리
24.05.24 C# 동시성 프로그래밍 (함수형 친화적 OOP) 본문
1. 비동기 인터페이스와 상속
async 키워드는 구현을 지니는 메서드에서만 적용할 수 있다. 즉, 기본 구현이 없는 추상 메서드나 인터페이스 메서드에는 적용할 수 없다. 하지만 async 키워드가 없어도 async 메서드와 시그니처가 똑같은 메서드를 정의할 순 있지만 대기하는 대상은 메서드가 아닌 형식을 대기하는 것이다.
interface IMyAsyncInterface
{
Task<int> CountBytesAsync(HttpClient client, string url);
}
class MyAsyncClass : IMyAsyncInterface
{
public async Task<int> CountBytesAsync(HttpClient client, string url)
{
var bytes = await client.GetByteArrayAsync(url);
return bytes.Length;
}
}
await Task UseMyInterfaceAsync(HttpClient client, IMyAsyncInterface service)
{
var result = await service.CountBytesAsync(client, "https://www.example.com");
Trace.WriteLine(result);
}
// 동기적으로 구현할 때
class MyAsyncClassStub : IMyAsyncInterface
{
public Task<int> CountBytesAsync(HttpClient client, string url)
{
return Task.FromResult(13);
}
}
2. 비동기 생성: 팩토리
생성자에서는 async/await 키워드를 사용할 수 없다. 그렇기에 형식을 팩토리로 만들어 사용하는 것이 좋다.
아예 정적 팩토리 메서드를 통해서만 생성자를 호출하도록 막는 것이다.
만약 DI 라이브러리 형태라던가 제어 역전 라이브러리는 적용할 수 없다.
class MyAsyncClass
{
private MyAsyncClass()
{
}
private async Task<MyAsyncClass> InitializeAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1));
return this;
}
public static Task<MyAsyncClass> CreateAsync()
{
var result = new MyAsyncClass();
return result.InitializeAsync();
}
}
3. 비동기 생성: 비동기 초기화 패턴
비동기 초기화가 필요한 형식이 있으면 마커 인터페이스에 정의한다.
public interface IAsyncInitialization
{
Task Initialization { get; }
}
public static class AsyncInitialization
{
public static Task WhenAllInitializedAsync(params object[] instances)
{
return Task.WhenAll(instances
.OfType<IAsyncInitialization>()
.Select(x => x.Initialization));
}
}
private async Task InitializationAsync()
{
await AsyncInitialization.WhenAllInitializedAsync(_fundamental,
_anotherType, _yetAnother);
}
4. 비동기 속성
기존 코드를 async로 변환하는 과정에서 많이 발생한다. getter 프로퍼티에서 자주 나오는데 읽을 때마다 비동기적으로 평가해야하는 값인지 한 번 비동기적으로 평가한 뒤에 나중에 사용할 수 있게 캐싱하는 값인지에 따라 구현이 다르다.
// 읽을 때마다 비동기적으로 평가 (비추천)
public async Task<int> GetDataAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1));
return 13;
}
public Task<int> Data
{
get { return GetDataAsync(); }
}
// 한 번 비동기적으로 평가한 뒹 나중에 사용할 수 있게 캐싱하는 값
public AsyncLazy<int> Data
{
get { return _data; }
}
private readonly AsyncLazy<int> _data =
new AsyncLazy<int>(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(1));
return 13;
});
5. 비동기 이벤트
async void의 반환 시점은 확인할 수 없다. 따라서 비동기 핸들러의 완료 시점을 확인할 수 없다. 단 Nito.AsyncEx 라이브러리에서는 디퍼럴이라는 개념을 통해서 이벤트 핸들러도 비동기적으로 운용할 수 있게 한다.
public class MyEventArgs : EventArgs, IDeferralSource
{
private readonly DeferralManager _deferrals = new DeferralManager();
...// 원하는 생성자와 속성을 추가한다.
public IDisposable GetDeferral()
{
return _deferrals.DeferralSource.GetDeferral();
}
internal Task WaitForDeferralsAsync()
{
return _deferrals.WaitForDeferralsAsync();
}
}
// 이벤트 인수 형식을 스레드로부터 안전하게 만드는 방법 중 가장 쉬운 것은
// 모든 속성을 읽기 전용으로 만드는 것이다.
public event EventHandler<MyEventArgs> MyEvent;
private async Task RaiseMyEventAsync()
{
EventHandler<MyEventArgs> handler = MyEvent;
if (handler == null)
{
return;
}
var args = new MyEventArgs(...);
handler(this, args);
await args.WaitForDeferralsAsync();
}
async void AsyncHandler(object sender, MyEventArgs args)
{
using IDisposable deferrals = args.GetDeferrals();
await Task.Delay(TimeSpan.FromSeconds(2));
}
6. 비동기 삭제
인스턴스를 삭제할 때 기존 작업에 적용할 취소 요청으로 취급할 수도 있고, 비동기 삭제를 구현할 수도 있다.
// 모든 기존 작업에 적용할 취소 요청
class MyClass : IDisposable
{
private readonly CancellationTokenSource _disposeCts =
new CancellationTokenSource();
public async Task<int> CalculateValueAsync()
{
using CancellationTokenSource combinedCts = CancellationTokenSource
.CreateLinkedTokenSource(cancellationToken, _disposeCts.Token);
await Task.Delay(TimeSpan.FromSeconds(2), combinedCts.Token);
return 13;
}
public void Dispose()
{
_disposeCts.Cancel();
}
}
async Task UseMyClassAsync()
{
Task<int> task;
using (var resource = new MyClass())
{
task = resource.CalculateValueAsync(default);
}
var result = awaiat task;
}
// 비동기 삭제
class MyClass : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await Task.Delay(TimeSpan.FromSeconds(2));
}
}
await using (var myClass = new MyClass())
{
///
} // DisposeAsync 호출 후 대기
var myClass = new MyClass();
await using (myClass.ConfigureAwait(false))
{
...
}
'C# > 동시성 처리' 카테고리의 다른 글
24.05.27 C# 동시성 프로그래밍 (스케줄링) (0) | 2024.05.27 |
---|---|
24.05.26 C# 동시성 프로그래밍 (동기화) (0) | 2024.05.26 |
24.05.22 C# 동시성 프로그래밍 (취소) (0) | 2024.05.22 |
24.05.21 C# 동시성 프로그래밍 (컬렉션) (0) | 2024.05.21 |
24.05.09 C# 동시성 프로그래밍 (상호운용) (0) | 2024.05.09 |
Comments