12. ValueTask vs Task (할당·풀링·"한 번만 소비" 규칙)
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 주제: ValueTask<T> vs Task<T> — 할당/풀링, 동기 완료 경로, "정확히 한 번만 소비" 규칙.
//
// 아래 코드의 동작과 위험을 예측·설명하라:
// (1) 캐시 적중(동기 완료) 시 ValueTask<T> 가 Task<T> 대비 무엇을 아끼는가? 게임 핫패스에서 왜 의미가 있나?
// (2) (A): 같은 ValueTask 를 두 번 await 하면 무엇이 잘못되는가? (특히 풀링되는 백킹일 때)
// (3) (B): ValueTask 를 변수에 담아 두고 나중에 .Result 로 읽으면?
// (4) ValueTask 를 여러 번/늦게 소비하고 싶을 땐 어떻게 해야 하나?
// (5) 메서드 반환 타입으로 Task 와 ValueTask 중 무엇을 언제 골라야 하나?
using System.Collections.Generic;
using System.Threading.Tasks;
public sealed class PlayerData { public long Id; }
public interface IPlayerDb { Task<PlayerData> FetchAsync(long id); }
public sealed class PlayerCache
{
private readonly Dictionary<long, PlayerData> _hot = new();
private readonly IPlayerDb _db;
public PlayerCache(IPlayerDb db) => _db = db;
// 캐시에 있으면 동기적으로 즉시 반환, 없으면 DB 비동기 조회.
public ValueTask<PlayerData> GetAsync(long id)
{
if (_hot.TryGetValue(id, out var p))
return new ValueTask<PlayerData>(p); // 동기 완료 경로
return new ValueTask<PlayerData>(LoadAsync(id)); // 비동기 경로
}
private async Task<PlayerData> LoadAsync(long id)
{
var data = await _db.FetchAsync(id);
_hot[id] = data;
return data;
}
}
public static class Consumer
{
public static async Task UseTwiceAsync(PlayerCache cache, long id)
{
ValueTask<PlayerData> vt = cache.GetAsync(id);
var first = await vt; // (A)
var second = await vt; // (A) 같은 vt 를 또 await
System.GC.KeepAlive(first); System.GC.KeepAlive(second);
}
public static async Task UseLaterAsync(PlayerCache cache, long id)
{
ValueTask<PlayerData> vt = cache.GetAsync(id);
await Task.Delay(100);
var data = vt.Result; // (B) 완료 전일 수 있는데 .Result 로 동기 접근
System.GC.KeepAlive(data);
}
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.