【Unity笔记】Unity超时检测器开发:支持自定义重试次数与事件触发
【Unity笔记】Unity超时检测器开发:支持自定义重试次数与事件触发
引言
超时检测器是 Unity 网络编程中的核心组件,用于监控异步操作(如 HTTP 请求、WebSocket 连接、AssetBundle 下载)的执行时间,防止长时间挂起导致应用卡死。超时检测器通过计时器 + 事件回调机制实现,支持自定义超时阈值、重试策略和异常处理,广泛应用于游戏联机、云存档、广告 SDK 集成等场景。
核心功能包括:超时检测、自动重试、事件通知、资源清理。基于 Unity 的协程系统和 C# 事件委托实现,时间复杂度 O(1),支持 Unity 2020.3+。超时阈值通常设为 5-30 秒,视网络环境调整。
核心设计原理
1. 超时检测机制
- 启动计时:异步操作开始时记录
startTime = Time.time
- 周期检查:每帧/固定间隔检查
Time.time - startTime > timeoutThreshold
- 超时触发:超出阈值时执行回调,取消原操作
- 嵌套协程:使用
yield return new WaitUntil
或自定义TimeoutCoroutine
2. 重试策略
策略 | 描述 | 适用场景 |
---|---|---|
无重试 | 超时即失败 | 实时性要求高 |
固定间隔 | 每次重试间隔固定 | 简单场景 |
指数退避 | 间隔呈指数增长 | 网络波动大 |
随机抖动 | 避免重试风暴 | 多客户端 |
基础超时检测器实现
1. 核心 TimeoutDetector 组件
using UnityEngine;
using System;
using System.Collections;
public class TimeoutDetector : MonoBehaviour
{
[System.Serializable]
public class TimeoutConfig
{
[Header("超时设置")]
public float timeoutSeconds = 10f;
public int maxRetries = 3;
public float retryDelay = 1f;
public RetryStrategy retryStrategy = RetryStrategy.ExponentialBackoff;
public enum RetryStrategy
{
None,
FixedDelay,
ExponentialBackoff,
RandomJitter
}
}
public TimeoutConfig config = new TimeoutConfig();
// 事件系统
public event Action<TimeoutResult> OnTimeout;
public event Action<TimeoutResult> OnSuccess;
public event Action<TimeoutResult> OnRetry;
public event Action<TimeoutResult> OnFinalFailure;
[System.Serializable]
public class TimeoutResult
{
public bool IsSuccess { get; set; }
public int RetryCount { get; set; }
public float ElapsedTime { get; set; }
public string ErrorMessage { get; set; }
public object CustomData { get; set; }
}
private Coroutine timeoutCoroutine;
/// <summary>
/// 启动带超时的异步操作
/// </summary>
public Coroutine StartWithTimeout(Func<IEnumerator> operation, object customData = null)
{
StopCurrentTimeout(); // 停止现有超时
TimeoutResult result = new TimeoutResult { CustomData = customData };
timeoutCoroutine = StartCoroutine(TimeoutWrapper(operation, result));
return timeoutCoroutine;
}
private IEnumerator TimeoutWrapper(Func<IEnumerator> operation, TimeoutResult result)
{
float startTime = Time.time;
int retryCount = 0;
while (retryCount <= config.maxRetries)
{
// 执行实际操作
IEnumerator operationCoroutine = operation();
bool operationCompleted = false;
float operationStartTime = Time.time;
// 超时检测协程
Coroutine timeoutCheck = StartCoroutine(CheckTimeout(startTime, () => {
operationCompleted = true;
result.ErrorMessage = "Operation timed out";
}));
// 等待操作完成或超时
yield return operationCoroutine;
StopCoroutine(timeoutCheck);
result.ElapsedTime = Time.time - operationStartTime;
if (!operationCompleted)
{
// 操作成功完成
result.IsSuccess = true;
OnSuccess?.Invoke(result);
yield break;
}
// 操作超时,处理重试
retryCount++;
result.RetryCount = retryCount;
OnTimeout?.Invoke(result);
OnRetry?.Invoke(result);
if (retryCount > config.maxRetries)
{
result.IsSuccess = false;
OnFinalFailure?.Invoke(result);
yield break;
}
// 计算重试延迟
float delay = CalculateRetryDelay(retryCount);
yield return new WaitForSeconds(delay);
startTime = Time.time; // 重置超时计时
}
}
private IEnumerator CheckTimeout(float startTime, Action onTimeout)
{
yield return new WaitUntil(() => Time.time - startTime > config.timeoutSeconds);
onTimeout?.Invoke();
}
private float CalculateRetryDelay(int retryCount)
{
switch (config.retryStrategy)
{
case TimeoutConfig.RetryStrategy.FixedDelay:
return config.retryDelay;
case TimeoutConfig.RetryStrategy.ExponentialBackoff:
return config.retryDelay * Mathf.Pow(2, retryCount);
case TimeoutConfig.RetryStrategy.RandomJitter:
return config.retryDelay + UnityEngine.Random.Range(0f, config.retryDelay);
default:
return 0f;
}
}
public void StopCurrentTimeout()
{
if (timeoutCoroutine != null)
{
StopCoroutine(timeoutCoroutine);
timeoutCoroutine = null;
}
}
void OnDestroy()
{
StopCurrentTimeout();
}
}
2. HTTP 请求超时示例
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
public class HTTPTimeoutExample : MonoBehaviour
{
public TimeoutDetector timeoutDetector;
public string apiUrl = "https://api.example.com/data";
[ContextMenu("Test HTTP with Timeout")]
void TestHTTPRequest()
{
StartCoroutine(HTTPRequestWithTimeout());
}
IEnumerator HTTPRequestWithTimeout()
{
// 配置超时:10秒,3次重试
timeoutDetector.config.timeoutSeconds = 10f;
timeoutDetector.config.maxRetries = 3;
timeoutDetector.config.retryStrategy = TimeoutDetector.TimeoutConfig.RetryStrategy.ExponentialBackoff;
return timeoutDetector.StartWithTimeout(() => MakeHTTPRequest(apiUrl));
}
private IEnumerator MakeHTTPRequest(string url)
{
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
// 设置超时(UnityWebRequest 自带超时,配合使用)
request.timeout = 8; // 8秒硬件超时
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
Debug.Log("HTTP Success: " + request.downloadHandler.text);
}
else
{
Debug.LogError("HTTP Error: " + request.error);
throw new System.Exception(request.error); // 触发超时检测
}
}
}
}
高级功能实现
3. AssetBundle 下载超时
public class AssetBundleTimeoutLoader : MonoBehaviour
{
public TimeoutDetector timeoutDetector;
public void LoadAssetBundleWithTimeout(string bundleURL, string bundleName)
{
timeoutDetector.StartWithTimeout(() => DownloadAssetBundle(bundleURL, bundleName));
}
private IEnumerator DownloadAssetBundle(string url, string bundleName)
{
using (UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url))
{
request.timeout = 30; // 30秒下载超时
yield return request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success)
{
yield break;
}
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
if (bundle == null)
{
Debug.LogError("Failed to load AssetBundle");
yield break;
}
// 使用 Bundle...
bundle.Unload(false);
}
}
}
4. WebSocket 连接超时
using WebSocketSharp;
using System;
public class WebSocketTimeoutExample : MonoBehaviour
{
public TimeoutDetector timeoutDetector;
private WebSocket ws;
public void ConnectWithTimeout(string wsUrl)
{
timeoutDetector.StartWithTimeout(() => ConnectWebSocket(wsUrl), wsUrl);
timeoutDetector.OnFinalFailure += OnConnectionFailed;
timeoutDetector.OnSuccess += OnConnectionSuccess;
}
private IEnumerator ConnectWebSocket(string url)
{
ws = new WebSocket(url);
var tcs = new System.Threading.Tasks.TaskCompletionSource<bool>();
ws.OnOpen += (sender, e) => {
tcs.SetResult(true);
};
ws.OnError += (sender, e) => {
tcs.SetException(new Exception(e.Message));
};
ws.Connect();
yield return tcs.Task;
}
private void OnConnectionSuccess(TimeoutDetector.TimeoutResult result)
{
Debug.Log("WebSocket connected successfully");
// 开始心跳检测
}
private void OnConnectionFailed(TimeoutDetector.TimeoutResult result)
{
Debug.LogError("WebSocket connection failed after timeout");
}
}
5. 自定义超时上下文管理
public class TimeoutContext<T>
{
public TimeoutDetector Detector { get; private set; }
public T OperationData { get; set; }
public bool IsActive => Detector.timeoutCoroutine != null;
public TimeoutContext(TimeoutDetector detector)
{
Detector = detector;
}
public Coroutine ExecuteWithTimeout(Func<IEnumerator> operation)
{
return Detector.StartWithTimeout(operation, this);
}
public void Cancel()
{
Detector.StopCurrentTimeout();
}
}
// 使用示例
public class GameSaveManager : MonoBehaviour
{
private TimeoutContext<SaveData> saveContext;
void Start()
{
saveContext = new TimeoutContext<SaveData>(timeoutDetector);
}
public void SaveGameWithTimeout(SaveData data)
{
saveContext.OperationData = data;
saveContext.ExecuteWithTimeout(() => SaveToCloud(data));
}
}
事件驱动与回调系统
6. 高级事件处理
public class AdvancedTimeoutEvents : MonoBehaviour
{
public TimeoutDetector timeoutDetector;
void Start()
{
// 注册详细事件
timeoutDetector.OnTimeout += HandleTimeout;
timeoutDetector.OnRetry += HandleRetry;
timeoutDetector.OnSuccess += HandleSuccess;
timeoutDetector.OnFinalFailure += HandleFinalFailure;
}
private void HandleTimeout(TimeoutDetector.TimeoutResult result)
{
Debug.LogWarning($"Operation timed out. Retry: {result.RetryCount}, Elapsed: {result.ElapsedTime}s");
// UI 反馈
ShowTimeoutWarning();
// 分析超时原因
AnalyzeTimeout(result);
}
private void HandleRetry(TimeoutDetector.TimeoutResult result)
{
float nextDelay = timeoutDetector.config.retryDelay * Mathf.Pow(2, result.RetryCount);
Debug.Log($"Retrying in {nextDelay}s... (Attempt {result.RetryCount}/{timeoutDetector.config.maxRetries})");
// 动态调整策略
if (result.RetryCount > 2)
{
timeoutDetector.config.retryStrategy = TimeoutDetector.TimeoutConfig.RetryStrategy.RandomJitter;
}
}
private void HandleSuccess(TimeoutDetector.TimeoutResult result)
{
Debug.Log($"Operation succeeded after {result.RetryCount} retries");
HideTimeoutWarning();
}
private void HandleFinalFailure(TimeoutDetector.TimeoutResult result)
{
Debug.LogError($"Final failure after {timeoutDetector.config.maxRetries} retries: {result.ErrorMessage}");
ShowOfflineMode();
}
}
性能优化与配置
7. 全局超时管理器
public class GlobalTimeoutManager : MonoBehaviour
{
public static GlobalTimeoutManager Instance;
[Header("全局配置")]
public TimeoutConfig defaultConfig;
public int maxConcurrentOperations = 10;
private TimeoutDetector[] detectors;
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
InitializeDetectors();
}
else
{
Destroy(gameObject);
}
}
private void InitializeDetectors()
{
detectors = new TimeoutDetector[maxConcurrentOperations];
for (int i = 0; i < maxConcurrentOperations; i++)
{
GameObject detectorObj = new GameObject($"TimeoutDetector_{i}");
detectorObj.transform.SetParent(transform);
detectors[i] = detectorObj.AddComponent<TimeoutDetector>();
detectors[i].config = defaultConfig;
}
}
public Coroutine ExecuteWithTimeout(Func<IEnumerator> operation, TimeoutConfig customConfig = null)
{
TimeoutDetector availableDetector = GetAvailableDetector();
if (availableDetector == null)
{
Debug.LogWarning("No available timeout detectors");
return null;
}
if (customConfig != null)
availableDetector.config = customConfig;
return availableDetector.StartWithTimeout(operation);
}
private TimeoutDetector GetAvailableDetector()
{
foreach (var detector in detectors)
{
if (detector.timeoutCoroutine == null)
return detector;
}
return null;
}
}
8. 配置优化建议
// 不同场景的推荐配置
public static class TimeoutPresets
{
public static TimeoutConfig CriticalOperation()
{
return new TimeoutConfig
{
timeoutSeconds = 5f, // 关键操作严格超时
maxRetries = 0, // 不重试
retryStrategy = TimeoutConfig.RetryStrategy.None
};
}
public static TimeoutConfig NetworkRequest()
{
return new TimeoutConfig
{
timeoutSeconds = 15f, // 网络请求宽松
maxRetries = 3,
retryStrategy = TimeoutConfig.RetryStrategy.ExponentialBackoff
};
}
public static TimeoutConfig AssetDownload()
{
return new TimeoutConfig
{
timeoutSeconds = 60f, // 大文件下载长超时
maxRetries = 2,
retryStrategy = TimeoutConfig.RetryStrategy.FixedDelay
};
}
}
使用示例与集成
9. 完整使用流程
public class GameNetworkManager : MonoBehaviour
{
private TimeoutDetector timeoutDetector;
void Start()
{
timeoutDetector = GetComponent<TimeoutDetector>();
// 玩家登录
LoginWithTimeout();
// 房间匹配
StartCoroutine(MatchmakingWithTimeout());
}
void LoginWithTimeout()
{
var config = new TimeoutDetector.TimeoutConfig
{
timeoutSeconds = 10f,
maxRetries = 2
};
timeoutDetector.config = config;
timeoutDetector.StartWithTimeout(LoginOperation);
}
private IEnumerator LoginOperation()
{
// 模拟登录请求
yield return new WaitForSeconds(UnityEngine.Random.Range(2f, 12f));
if (UnityEngine.Random.value > 0.7f) // 30% 模拟失败
throw new System.Exception("Login failed");
}
}
调试与监控
10. 超时统计与日志
public class TimeoutMonitor : MonoBehaviour
{
[System.Serializable]
public class TimeoutStats
{
public int totalOperations;
public int timeoutCount;
public int successCount;
public int retryCount;
public float avgResponseTime;
}
public TimeoutStats stats = new TimeoutStats();
public void LogTimeout(TimeoutDetector.TimeoutResult result)
{
stats.totalOperations++;
if (!result.IsSuccess)
stats.timeoutCount++;
else
stats.successCount++;
stats.retryCount += result.RetryCount;
stats.avgResponseTime = (stats.avgResponseTime * (stats.totalOperations - 1) + result.ElapsedTime) / stats.totalOperations;
// 持久化统计
PlayerPrefs.SetInt("Timeout_Total", stats.totalOperations);
}
}
常见问题解决
问题 | 原因 | 解决方案 |
---|---|---|
协程提前结束 | 超时检测与操作协程竞争 | 使用嵌套协程统一控制 |
重试过于频繁 | 策略配置不当 | 使用指数退避 + 抖动 |
内存泄漏 | 事件未注销 | 在 OnDestroy 中清理事件 |
平台差异 | Time.time vs UnscaledTime | 根据需求选择时间源 |
多线程冲突 | 异步操作线程安全 | 使用主线程回调 |
此超时检测器提供完整的异步操作监控方案,支持灵活配置和事件驱动。如需特定网络库集成(如 Photon、Mirror)或性能调优,请提供更多细节!