36. 강화 결과 완료 콜백과 취소의 경합

난이도 최상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// 시나리오 (서버 내부): 아이템 강화 요청을 비동기로 처리하는 컴포넌트.
// 강화 요청이 들어오면 백그라운드에서 강화 결과(성공/실패, 결과 아이템)를
// 계산하는 작업(Task)을 시작하고, 그 작업이 끝나면 콜백으로 최종 결과를
// 플레이어 컨텍스트(캐릭터 상태)에 반영한다.
//
// 강화 계산은 시간이 걸릴 수 있어(예: 원거리 조회 포함), 플레이어가 그 사이
// 접속을 끊거나 다른 사유로 요청을 취소(Cancel)할 수 있다. 취소되면 해당
// 요청에 연결된 컨텍스트(결과를 받아 반영할 대상)는 더 이상 유효하지 않은
// 것으로 간주하고 정리(해제)된다.
//
// 요구사항:
//   - 취소된 요청의 결과는 어떤 경로로도 최종 상태에 반영되면 안 된다.
//   - 취소 처리와 작업의 자연 완료가 동시에 일어나더라도(경합하더라도),
//     이미 정리된 컨텍스트에 완료 콜백이 접근해서는 안 된다.
//   - 취소되지 않고 정상 완료된 요청은 결과가 반드시, 정확히 한 번 반영되어야
//     한다.
//
// 과제: (A)(B) 지점을 검토해 위 요구사항이 깨지는 경로를 찾고 수정하라.

using System;
using System.Threading;
using System.Threading.Tasks;

public sealed class EnhancementContext
{
    public int CharacterId;
    public int ResultItemId;
    public bool Applied;

    public void ApplyResult(int resultItemId)
    {
        // 실제로는 캐릭터 인벤토리/스탯을 갱신하는 로직이 들어간다.
        ResultItemId = resultItemId;
        Applied = true;
    }

    public void Dispose()
    {
        // 컨텍스트가 들고 있던 리소스(예: DB 세션 핸들, 캐시 엔트리)를 반환한다.
        CharacterId = -1;
    }
}

public sealed class EnhancementRequest
{
    public EnhancementContext Context; // (A)
    public bool IsCancelled;
    public Task ComputeTask;
}

public sealed class EnhancementService
{
    // 강화 요청을 시작한다. 계산이 끝나면 콜백에서 컨텍스트에 결과를 반영한다.
    public EnhancementRequest Start(EnhancementContext context)
    {
        var request = new EnhancementRequest { Context = context };

        request.ComputeTask = Task.Run(() =>
        {
            // 강화 결과 계산(원거리 조회 등을 흉내낸 지연).
            int resultItemId = ComputeEnhancementResult(context.CharacterId);

            // 계산이 끝나면 컨텍스트에 결과를 반영한다.
            // 이 시점에 Cancel 이 동시에 호출되어 컨텍스트가 이미 정리되었을 수
            // 있다는 사실을 이 콜백은 알지 못한다.
            request.Context.ApplyResult(resultItemId); // (B)
        });

        return request;
    }

    // 요청을 취소한다. 컨텍스트를 정리해 더 이상 쓰이지 않게 한다.
    public void Cancel(EnhancementRequest request)
    {
        request.IsCancelled = true;
        request.Context.Dispose();
        request.Context = null;
    }

    private static int ComputeEnhancementResult(int characterId)
    {
        Thread.Sleep(50); // 계산/조회 지연 흉내.
        return characterId * 7 % 100;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.