본문 바로가기
개발/Unity

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

by 테샤르 2025. 2. 13.

특정 주기마다 호출되는 코드 만들기 (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);

 

 

★☆☆☆☆

 

반응형

댓글