using System;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace JDWTaskSystem
{
/// <summary>
/// 연속 작업 실행 래퍼 (박싱 방지) – 풀링을 위해 제공
/// 외부에는 읽기 전용 인터페이스를 제공하며, 내부에서는 풀 재사용을 위해 상태를 재설정할 수 있도록 제공
/// </summary>
public sealed class ContinuationBox : ITaskCompletionAction
{
private Action continuation; // 실행할 연속 작업
private DateTime lastUsed; // 마지막 사용 시각
private int usageCount; // 사용 횟수 추적
private const int UsageThreshold = 100; // 사용 횟수 임계값
private static readonly Action emptyAction = () => { }; // 기본 빈 액션 (NullReference 방지)
/// <summary>
/// 등록된 연속 작업(continuation)을 반환
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Action GetContinuation()
{
return continuation;
}
/// <summary>
/// 마지막 사용 시각 (UTC)을 반환
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DateTime GetLastUsed()
{
return lastUsed;
}
/// <summary>
/// 내부용 세터: continuation을 설정
/// </summary>
/// <param name="value">설정할 액션</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void SetContinuation(Action value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
continuation = value;
}
/// <summary>
/// 내부용 세터: 마지막 사용 시각을 설정
/// </summary>
/// <param name="value">설정할 시각</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void SetLastUsed(DateTime value)
{
lastUsed = value;
}
/// <summary>
/// 파라미터 없는 생성자 – 풀에서 인수 없이 생성될 때 사용되며, 이후 Reinitialize로 상태를 설정
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ContinuationBox()
{
continuation = emptyAction; // 기본적으로 빈 액션으로 초기화
lastUsed = DateTime.UtcNow; // 현재 시각으로 설정
usageCount = 0; // 사용 횟수 초기화
}
/// <summary>
/// 생성자 – 지정된 연속 작업을 설정하고, 마지막 사용 시각을 현재로 초기화
/// </summary>
/// <param name="continuation">실행할 액션</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ContinuationBox(Action continuation)
{
SetContinuation(continuation); // 주어진 액션 설정
lastUsed = DateTime.UtcNow; // 현재 시각으로 설정
usageCount = 0; // 사용 횟수 초기화
}
/// <summary>
/// 내부용 재초기화 메서드 – 풀에서 재사용할 때 새로운 연속 작업과 함께 사용 횟수를 추적하여, 일정 횟수 이상 사용된 경우에만 마지막 사용 시각을 갱신
/// </summary>
/// <param name="continuation">새로운 액션</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Reinitialize(Action continuation)
{
SetContinuation(continuation); // 새로운 액션 설정
usageCount++; // 사용 횟수 증가
if (usageCount >= UsageThreshold) // 임계값 도달 시
{
SetLastUsed(DateTime.UtcNow); // 마지막 사용 시각 갱신
usageCount = 0; // 사용 횟수 리셋
}
}
/// <summary>
/// 내부용 Reset 메서드 – 풀에서 반환할 때 상태를 초기화
/// continuation은 빈 액션(() => {})으로 설정하여 NullReferenceException을 방지
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Reset()
{
continuation = emptyAction; // 빈 액션으로 리셋
usageCount = 0; // 사용 횟수 초기화
SetLastUsed(DateTime.UtcNow); // 마지막 사용 시각 갱신
}
/// <summary>
/// ITaskCompletionAction 인터페이스 구현 – 등록된 연속 작업을 실행한 후, 자동으로 풀에 반환
/// </summary>
/// <param name="source">작업 소스 (필요시 참조용)</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(ITaskSource source)
{
try
{
Action cont = GetContinuation();
if (cont == null)
{
Debug.LogError("Continuation is null in ContinuationBox!");
return;
}
cont.Invoke();
}
catch (Exception ex)
{
Debug.LogError($"Continuation execution failed in ContinuationBox: {ex.Message}\nStackTrace: {ex.StackTrace}");
throw; // 상위로 전파해 디버깅 용이
}
finally
{
ContinuationPool.Return(this); // 실행 후 풀에 반환
}
}
}
}
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using UnityEngine;
namespace JDWTaskSystem
{
/// <summary>
/// 비동기 작업의 기본 TaskSource 클래스
/// TaskSource는 비동기 작업의 상태, 결과, 완료 콜백을 관리함.
/// 풀링 최적화와 할당 최소화를 위해 설계됨.
/// </summary>
/// <typeparam name="TResult">비동기 작업 결과 타입</typeparam>
public abstract class TaskSourceBase<TResult> : ITaskSource<TResult>, ITaskPoolable
{
// 버전 관리를 위해 long 사용 (오버플로우 방지)
protected long version;
protected TaskStatus currentState;
protected TResult result;
protected Exception exception;
// 완료 콜백을 관리할 List; 이전 작업의 잔재 제거를 위해 OnAcquireFromPool에서 새로 초기화함.
private List<ITaskCompletionAction> continuations;
private readonly object continuationsLock = new object();
private bool isInPool; // 풀 상태 여부
// 정적 예외 객체 (할당 최소화를 위해)
private static readonly Exception TaskFailed = new Exception("Task failed");
private static readonly OperationCanceledException TaskCanceled = new OperationCanceledException();
#region Core Methods
/// <summary>
/// 현재 Task 버전을 반환함.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long GetVersion() => Interlocked.Read(ref version);
/// <summary>
/// 풀에서 가져올 때 초기화함.
/// </summary>
public virtual void OnAcquireFromPool()
{
SetIsInPool(false);
Interlocked.Increment(ref version);
Interlocked.MemoryBarrier();
currentState = TaskStatus.Pending;
result = default(TResult);
exception = null;
// 새 리스트로 초기화하여 이전 콜백 잔재 제거
continuations = new List<ITaskCompletionAction>();
}
/// <summary>
/// 풀로 돌려줄 때 정리함.
/// </summary>
public virtual void OnReleaseToPool()
{
SetIsInPool(true);
result = default(TResult);
exception = null;
continuations = null;
}
/// <summary>
/// 풀에 있는지 여부를 반환함.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool GetIsInPool() => isInPool;
/// <summary>
/// 객체의 풀 상태를 설정함.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetIsInPool(bool value) => isInPool = value;
/// <summary>
/// 현재 Task 상태를 반환함. 토큰이 일치하지 않으면 Faulted로 처리함.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TaskStatus GetStatus(long token)
{
return token != Interlocked.Read(ref version) ? TaskStatus.Faulted : currentState;
}
/// <summary>
/// Task 완료 시 실행할 콜백을 등록함.
/// 토큰이나 상태가 맞지 않으면 즉시 콜백 실행함.
/// </summary>
public virtual void OnCompleted(ITaskCompletionAction continuation, long token)
{
if (token != Interlocked.Read(ref version) || currentState != TaskStatus.Pending)
{
continuation.Invoke(this);
return;
}
lock (continuationsLock)
{
// continuations가 null인 경우는 거의 없지만, 안전을 위해 검사
if (continuations == null)
{
Debug.LogWarning("Continuations list is null in OnCompleted, invoking immediately.");
continuation.Invoke(this);
}
else
{
continuations.Add(continuation);
}
}
}
/// <summary>
/// Task 결과를 반환함.
/// 상태에 따라 결과를 반환하거나 예외를 던짐.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TResult GetResult(long token)
{
if (token != Interlocked.Read(ref version))
{
throw new InvalidOperationException("Invalid task version");
}
return currentState switch
{
TaskStatus.Succeeded => result,
TaskStatus.Faulted => throw exception ?? TaskFailed,
TaskStatus.Canceled => throw TaskCanceled,
_ => throw new InvalidOperationException("Task not completed")
};
}
/// <summary>
/// Task를 성공 상태로 전환하고 결과를 저장하며 등록된 콜백을 실행함.
/// </summary>
public virtual bool TrySetResult(TResult value)
{
lock (this)
{
if (currentState != TaskStatus.Pending)
return false;
currentState = TaskStatus.Succeeded;
result = value;
ExecuteContinuations();
return true;
}
}
/// <summary>
/// Task를 실패 상태로 전환하고 예외를 저장하며 등록된 콜백을 실행함.
/// </summary>
public virtual bool TrySetException(Exception ex)
{
lock (this)
{
if (currentState != TaskStatus.Pending)
return false;
currentState = TaskStatus.Faulted;
exception = ex ?? TaskFailed;
ExecuteContinuations();
return true;
}
}
/// <summary>
/// Task를 취소 상태로 전환하고 등록된 콜백을 실행함.
/// </summary>
protected virtual bool TrySetCanceled(CancellationToken cancellationToken)
{
lock (this)
{
if (currentState != TaskStatus.Pending)
return false;
currentState = TaskStatus.Canceled;
exception = new OperationCanceledException(cancellationToken);
ExecuteContinuations();
return true;
}
}
/// <summary>
/// Task 상태를 초기화함. (재사용을 위해)
/// </summary>
protected virtual void ResetState()
{
currentState = TaskStatus.Pending;
version = 0;
result = default(TResult);
exception = null;
continuations?.Clear();
}
/// <summary>
/// 등록된 모든 콜백을 실행함.
/// </summary>
private void ExecuteContinuations()
{
List<ITaskCompletionAction> toExecute = null;
lock (continuationsLock)
{
if (continuations != null && continuations.Count > 0)
{
// 안전한 스냅샷 복사
toExecute = new List<ITaskCompletionAction>(continuations);
continuations.Clear();
}
}
if (toExecute != null)
{
foreach (var callback in toExecute)
{
try
{
if (callback == null)
{
Debug.LogWarning("Null callback found in continuations list, skipping.");
continue;
}
ContinuationRunner.RunContinuation(callback);
}
catch (Exception ex)
{
Debug.LogError($"Continuation execution failed in TaskSourceBase: {ex.Message}\nStackTrace: {ex.StackTrace}\nCallback Type: {callback?.GetType()}");
// 상위로 전파하지 않고 로그만 남김
}
}
}
}
/// <summary>
/// Task 결과의 유효성을 검사함.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ValidateResult(long token)
{
if (token != Interlocked.Read(ref version))
throw new InvalidOperationException("Task version mismatch");
}
/// <summary>
/// Task에서 발생한 예외를 반환함.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Exception TryGetException(long token)
{
return token != Interlocked.Read(ref version)
? new InvalidOperationException("Task version mismatch")
: exception;
}
#endregion
#region ITaskSource & ITaskPoolable 구현
void ITaskSource.Reset(long token) => OnAcquireFromPool();
void ITaskSource.Cleanup(long token) => OnReleaseToPool();
long ITaskSource.GetVersion() => GetVersion();
#endregion
}
}
ContinuationBox에서 Invoke()에서 예외 던지는거보면
비동기 실행시 콜백(Continuation) 저장하고 실행하는 과정에서
여기서 뭔가 에러가 있는듯한데
모르겄다.
댓글 영역
획득법
① NFT 발행
작성한 게시물을 NFT로 발행하면 일주일 동안 사용할 수 있습니다. (최초 1회)
② NFT 구매
다른 이용자의 NFT를 구매하면 한 달 동안 사용할 수 있습니다. (구매 시마다 갱신)
사용법
디시콘에서지갑연결시 바로 사용 가능합니다.