목록전체 글 (247)
방프리
Item 35 : PLINQ가 병렬 알고리즘을 구현하는 방법을 이해하라 PLINQ를 사용하면 병렬로 처리하기 때문에 속도가 향상되지만 무조건적으로는 아니다. 어느 정도 계산을 분배한 뒤 동작하는 방식인데 제대로 사용하지 않으면 분할에 오히려 작업 속도가 더 많이 소요되어 PLINQ를 사용하지 않은 것보다 더 오래 걸릴 수 있다. PLINQ를 분할하는 방식은 아래 중 하나를 채용한다. 범위 분할 덩어리 분할 줄 단위 분할 해시 분할 1. 범위분할 가장 단순한 알고리즘으로 동작한다. 입력값의 범위를 태스크 수로 나누고, 나눈 집합을 각 태스크에 할당한다. 대신 분할하는 작업 때문에 배열형태인 IList 형태만 지원한다. 2. 덩어리 분할 대스크가 추가 작업을 요구할 때마다 청크를 할당하는 방식. 작은 청크 ..
Item 34 : 비동기 메서드의 반환값을 캐시하는 경우 ValueTask를 사용하라 Task는 사용될 때마다 Task 인스턴스를 생성한다. 이조차도 비용이 될 수도 있고 만약 자주 호출되는 API일 경우 호출 될 때마다 계산하여 가져와야 하는 데이터라면 연산량을 줄이기 위해 결과값을 미리 캐싱하는 경우가 있다. 이 결과값을 비동기 메서드로 가져올 때 ValueTask가 빛을 발휘한다. public ValueTask RetrieveHistoricalData() { if (DateTime.Now - lastReading > TimeSpan.FromMinutes(5)) { return new ValueTask(recentObservations); } else { async Task loadCache() { ..
Item 33 : 태스크 취소 프로토콜 구현을 고려하라 태스크 실행 도중에 문제가 생긴다면 메서드 호출 도중 취소 처리를 하는 방식도 고려해야 한다. 주로 CancellationToken을 활용하여 태스크 취소 처리를 진행한다. public class ProgressReporter : IProgress { public void Report((int percent, string message) value) { WriteLine($"{value.Percent} completed: {value.message}"); } } await generator.RunPayroll(DateTime.Now, new ProgressReporter()); public Task RunPayroll(DateTime payrollP..
Item 32 : 비동기 작업은 태스크 객체를 사용해 구성하라 태스크란 다른 리소스에 작업을 위임할 수 있도록 추상화한 개념이다. 태스크는 자기의 작업이 완료될 때 Task 객체를 반환하는데 이를 반복문에 사용하거나 여러 태스크를 처리하는 로직을 사용했을 때 대기를 해야하는 경우가 있다. 특히 Task.WhenAny()는 호출할 때마다 새로운 태스크를 생성하기에 TaskCompletionSource를 통해 관리를 할 수 있다. public static Task[] OrderByCompletion( this IEnuemerable tasks) { var sourceTask = tasks.ToList(); var completionSources = new TaskCompletionSource[sourceTa..
Item 31 : 불필요한 콘텍스트 마셜링을 피하라 어떤 콘텍스트에서도 실행될 수 있는 코드를 '자유 코드' 라고 한다. 기본적으로 개발자가 작성하는 코드는 콘텍스트 자유 코드이다. 반대로 특정 SynchronizationContext에서만 실행될 수 있는 코드를 '콘텍스트 인식코드'라고 한다. 콘텍스트 인식코드는 주로 GUI, HTTPContext 등에 사용되며 이 콘텍스트들은 다른 콘텍스트와 스위칭 되며 실행되도록 해야한다. 콘텍스트를 전환해서 문제를 피하는 것이 콘텍스트를 전환하지 않아 발생하는 문제를 해결하는 것보다 더 나은 선택이기 때문이다. 이를 위해 ConfigureAwait() 메서드를 사용한다. public static async Task ReadPacket(string url) { va..
Item 30 : 비동기 메서드를 사용해서 스레드 생성과 콘텍스트 전환을 피하라 비동기 메서드는 만능이 아니다. 로직의 수행 시간이 길면 그만큼 결과 도출이 늦어지기 때문에 비동기 메서드를 사용했다고 해서 복잡한 로직이 빠르게 수행된다는 생각을 가지면 안된다. 또한 잘 못된 생각으로 비동기 메서드에서 스레드를 생성하거나 다른 스레드로 콘텍스트를 전환한다고 해서 성능이 더 좋아지는 것도 아니다. 상황 별로 나누어보자 1. 파일 입출력은 비동기이지만 스레드가 아닌 I/O 완료 포트를 사용 2. 웹 요청도 비동기이지만 스레드가 아닌 네트워크 인터럽트 사용 3. GUI 어플리케이션은 UI 스레드가 메인 스레드이므로 다른 스레드에게 CPU 작업을 요청할 수 있음 (단, 실제로 적용하게 된다면 CPU 작업이 완료되..
Item 29 : 동기, 비동기 메서드를 함께 사용해서는 안 된다. 비동기 메서드와 동기 메서드를 사용할 때 두 가지 규칙을 지켜야 한다. 1. 비동기 작업이 완료될 때까지 기다리는 동기 메서드를 만들지 말라. 2. 수행 시간이 오래 걸리는 CPU 중식 작업을 비동기로 수행하지 말라. 위 규칙을 지키지 않으면 교착상태 (DeadLock)에 빠지기 쉽다. public static async Task ComputeUsageAsync() { try { var operand = await GetLeftOperandForIndex(19); var operand2 = await GetRightOperandForIndex(23); return operand + operand2; } catch (KeyNotFoundE..
Item 28 : async void 메서드는 절대 작성하지 말라 해당 챕터는 C#의 동시성 처리 관련 책을 읽었을 때 그 저자도 강조하던 멘트였다. (Link : C# 동시성 프로그래밍 (Concurrency in C# Cookbook) https://book.interpark.com/product/BookDisplay.do?_method=detail&sc.saNo=001&sc.prdNo=349595515&product2020=true) 그 이유는 async void는 대기할 수 없다. 즉, 앞 챕터에서 이야기 했던 async await의 장점을 살릴 수가 없다. 그럼에도 불가피하게 사용해야하는 경우가 있는데 바로 비동기 이벤트 핸들러를 만들 때이다. async void를 사용하지 않아야 하는 이유 중..