개발/Unity

Unity) 특정 주기마다 호출되는 코드 만들기 (PeriodicInvoker)

테샤르 2025. 2. 13. 21:52

특정 주기마다 호출되는 코드 만들기 (PeriodicInvoker)

 

간단하게 특정 주기마다만 호출되는 코드를 만들고 싶었다.

1초마다 호출되는 타이머 같은 기능이다.

 

반응형

 

< PeriodicInvoker 코드 >

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Timers; // System.Timers.Timer 사용을 위한 네임스페이스
using UnityEngine;

public class PeriodicInvoker
{
	private readonly ConcurrentDictionary<Guid, ScheduledTask> _tasks = new();
	private readonly System.Timers.Timer _timer; // System.Timers.Timer 명시적으로 사용
	private CancellationTokenSource _cancellationTokenSource = new();

	public PeriodicInvoker(double resolutionMilliseconds = 100)
	{
		if (resolutionMilliseconds <= 0)
			throw new ArgumentException("Resolution must be greater than zero.", nameof(resolutionMilliseconds));

		_timer = new System.Timers.Timer(resolutionMilliseconds) // System.Timers.Timer 사용
		{
			AutoReset = true,
			Enabled = false
		};
		_timer.Elapsed += OnElapsed;
	}

	private void OnElapsed(object? sender, ElapsedEventArgs e)
	{
		if (_cancellationTokenSource.IsCancellationRequested)
		{
			Stop();
			return;
		}

		var now = DateTime.UtcNow;
		foreach (var taskPair in _tasks)
		{
			var task = taskPair.Value;

			if (now >= task.NextExecutionTime)
			{
				if (task.CancellationToken.IsCancellationRequested)
				{
					_tasks.TryRemove(taskPair.Key, out _); // 취소된 작업 제거
					continue;
				}

				try
				{
					UnityMainThreadDispatcher.Instance().Enqueue(() => task.Callback?.Invoke());

					if (task.IsOneTime)
					{
						_tasks.TryRemove(taskPair.Key, out _); // 한 번만 실행하는 작업은 제거
					}
					else
					{
						task.NextExecutionTime = now.AddMilliseconds(task.IntervalMilliseconds);
					}
				}
				catch (Exception ex)
				{
					Debug.LogError($"Task Execution Error: {ex.Message}");
					task.OnError?.Invoke(ex);
				}
			}
		}
	}

	/// <summary>
	/// 주기적 작업을 등록합니다.
	/// </summary>
	public Guid RegisterTask(double intervalMilliseconds, Action callback, CancellationToken cancellationToken, Action<Exception>? onError = null)
	{
		if (intervalMilliseconds <= 0)
			throw new ArgumentException("Interval must be greater than zero.", nameof(intervalMilliseconds));
		if (callback == null)
			throw new ArgumentNullException(nameof(callback));

		var task = new ScheduledTask
		{
			IntervalMilliseconds = intervalMilliseconds,
			Callback = callback,
			OnError = onError,
			NextExecutionTime = DateTime.UtcNow.AddMilliseconds(intervalMilliseconds),
			IsOneTime = false,
			CancellationToken = cancellationToken
		};

		var id = Guid.NewGuid();
		_tasks[id] = task;
		return id;
	}

	/// <summary>
	/// 특정 시간 뒤 한 번만 실행되는 작업을 등록합니다.
	/// </summary>
	public Guid RegisterDelayedTask(double delayMilliseconds, Action callback, CancellationToken cancellationToken, Action<Exception>? onError = null)
	{
		if (delayMilliseconds <= 0)
			throw new ArgumentException("Delay must be greater than zero.", nameof(delayMilliseconds));
		if (callback == null)
			throw new ArgumentNullException(nameof(callback));

		var task = new ScheduledTask
		{
			IntervalMilliseconds = delayMilliseconds,
			Callback = callback,
			OnError = onError,
			NextExecutionTime = DateTime.UtcNow.AddMilliseconds(delayMilliseconds),
			IsOneTime = true,
			CancellationToken = cancellationToken
		};

		var id = Guid.NewGuid();
		_tasks[id] = task;
		return id;
	}

	/// <summary>
	/// 등록된 작업을 수동으로 제거합니다.
	/// </summary>
	public void UnregisterTask(Guid taskId)
	{
		_tasks.TryRemove(taskId, out _);
	}

	/// <summary>
	/// 모든 작업을 취소하고 종료합니다.
	/// </summary>
	public void CancelAllTasks()
	{
		_cancellationTokenSource.Cancel();
		_tasks.Clear();
	}

	public void Start() => _timer.Start();
	public void Stop() => _timer.Stop();

	private void OnDestroy()
	{
		Dispose();
	}

	public void Dispose()
	{
		Stop();
		_timer.Dispose();
		_cancellationTokenSource.Cancel();
		_cancellationTokenSource.Dispose();
		_tasks.Clear();
	}

	private class ScheduledTask
	{
		public double IntervalMilliseconds { get; set; }
		public Action? Callback { get; set; }
		public Action<Exception>? OnError { get; set; }
		public DateTime NextExecutionTime { get; set; }
		public bool IsOneTime { get; set; }
		public CancellationToken CancellationToken { get; set; }
	}
}

 

반응형

 

< 1초마다 호출되는 코드 >

public void PlayStart()
{
	timer = new PeriodicInvoker(1000); 
	timerID = timer.RegisterTask(1000, () => 
	{
		time ++;
		timerAction?.Invoke(time);

	}, CancellationToken.None);
	timer.Start();
}

 

< 종료 >

	if (timerID != Guid.Empty)
		timer.UnregisterTask(timerID);

 

 

★☆☆☆☆

 

반응형