You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中如何将ISynchronizeInvoke设置到主线程(SynchronizationContext.Current)?

在Unity中让System.Timers.Timer在主线程执行的解决方案

System.Timers.Timer默认会在线程池线程触发Elapsed事件,而Unity的大部分API(比如修改Transform、调用GameObject方法)只能在主线程执行,直接在事件里调用会抛出线程安全异常。下面是两种经过验证的可行方案:

方案一:主线程任务队列(最可靠)

核心思路是把Timer触发的Unity操作放到线程安全的队列中,然后在Unity主线程的Update循环里取出并执行。

实现步骤:

  1. 创建一个主线程调度器类,负责维护任务队列并在Update中处理:
using System.Collections.Concurrent;
using UnityEngine;

public class MainThreadDispatcher : MonoBehaviour
{
    private static MainThreadDispatcher _instance;
    private readonly ConcurrentQueue<System.Action> _tasks = new ConcurrentQueue<System.Action>();

    public static MainThreadDispatcher Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<MainThreadDispatcher>();
                if (_instance == null)
                {
                    GameObject dispatcherObj = new GameObject("MainThreadDispatcher");
                    _instance = dispatcherObj.AddComponent<MainThreadDispatcher>();
                    DontDestroyOnLoad(dispatcherObj);
                }
            }
            return _instance;
        }
    }

    public void Enqueue(System.Action task)
    {
        _tasks.Enqueue(task);
    }

    private void Update()
    {
        while (_tasks.TryDequeue(out var task))
        {
            task.Invoke();
        }
    }
}
  1. 使用System.Timers.Timer时,在Elapsed事件中将Unity操作加入队列:
using System.Timers;
using UnityEngine;

public class TimerExample : MonoBehaviour
{
    private Timer _timer;

    private void Start()
    {
        _timer = new Timer(2000); // 2秒触发一次
        _timer.Elapsed += OnTimerElapsed;
        _timer.AutoReset = true;
        _timer.Start();
    }

    private void OnTimerElapsed(object sender, ElapsedEventArgs e)
    {
        // 将需要在主线程执行的操作加入队列
        MainThreadDispatcher.Instance.Enqueue(() =>
        {
            Debug.Log("Timer触发,在主线程执行操作");
            transform.position += Vector3.up; // 示例:修改Transform
        });
    }

    private void OnDestroy()
    {
        _timer?.Stop();
        _timer?.Dispose();
    }
}

方案二:正确捕获并使用Unity主线程的SynchronizationContext

之前你尝试的SynchronizationContext.Current.Post无效,大概率是因为没有在主线程初始化阶段正确捕获上下文。Unity的主线程SynchronizationContext需要在MonoBehaviour的生命周期方法(比如Awake、Start)中获取,不能在子线程中获取。

实现步骤:

  1. 在主线程中捕获SynchronizationContext:
using System.Threading;
using System.Timers;
using UnityEngine;

public class TimerWithContext : MonoBehaviour
{
    private Timer _timer;
    private SynchronizationContext _mainThreadCtx;

    private void Awake()
    {
        // 必须在主线程的生命周期方法中获取
        _mainThreadCtx = SynchronizationContext.Current;
    }

    private void Start()
    {
        _timer = new Timer(1500);
        _timer.Elapsed += OnTimerElapsed;
        _timer.AutoReset = true;
        _timer.Start();
    }

    private void OnTimerElapsed(object sender, ElapsedEventArgs e)
    {
        // 使用捕获的主线程上下文Post任务
        _mainThreadCtx.Post(_ =>
        {
            Debug.Log("通过SynchronizationContext在主线程执行");
            GetComponent<Renderer>().material.color = Color.RandomHSV(); // 示例:修改材质颜色
        }, null);
    }

    private void OnDestroy()
    {
        _timer?.Stop();
        _timer?.Dispose();
    }
}

注意事项:

  • 不要在子线程中调用SynchronizationContext.Current,此时获取到的是线程池线程的上下文,不是主线程的。
  • 如果你的Unity版本较旧(比如2019之前),可能需要手动安装System.Threading.Tasks.Extensions包来确保SynchronizationContext正常工作。

为什么不直接设置Timer的SynchronizingObject?

System.Timers.Timer的SynchronizingObject属性需要实现ISynchronizeInvoke接口,而Unity的MonoBehaviour并没有实现这个接口,所以无法直接赋值。上面两种方案是更适配Unity生态的替代方案。

内容的提问来源于stack exchange,提问作者CaseyHofland

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 20:32:50