Is there anyway to invoke unity functions on the main thread? I’m building an Editor script that runs workers in background threads due to large processing loads so as not to freeze up the editor. I need to be able to call unity functions on the final result but this needs to run in the main thread. Is there a way to do this?
Hey guys,
I created a simple class for this reason that you can call with a one-liner.
You can use it like this:
public IEnumerator ThisWillBeExecutedOnTheMainThread() {
Debug.Log ("This is executed from the main thread");
yield return null;
}
public void ExampleMainThreadCall() {
UnityMainThreadDispatcher.Instance().Enqueue(ThisWillBeExecutedOnTheMainThread());
}
Simply head over to GitHub - PimDeWitte/UnityMainThreadDispatcher: A simple, thread-safe way of executing actions (Such as UI manipulations) on the Unity Main Thread and start using it if you’d like.
You cannot really call into a running thread; instead, the thread you want to call (the main thread in your case) needs to maintain a queue of events. Other threads can push events into the queue and the owning thread will consume them from time to time. This is called the producer-consumer-pattern. An ‘event’ in this context is just a data structure that contains whatever information you need to send to the other thread.
That implies, of course, that the main thread will not receive the call immediately (synchronously) but with a delay. Well, that is the nature of threading. You cannot ‘interrupt’ a thread to make a call into it. If you need to pass data in both directions both threads will need a queue.
The queue will need to be thread-safe, of course. Simply lock it whenever you access it.
.NET has a thread dispatcher class that implements that pattern in a nice way and it even feels like making function calls. I am not sure you can use it in Unity, though, since every thread may only have one dispatcher and the Unity main thread may already have one. May be worth trying, though.
http://msdn.microsoft.com/de-de/library/system.windows.threading.dispatcher%28v=vs.85%29.aspx
If it does not work you will have to implement your own queue solution.
You can easily create a dispatcher to talk to Unity’s main loop by having a component read from a list of available actions. This Unity Gems Threading Article contains a package that can be used to achieve this.
It is used inconjunction with threads like this:
//Scale a mesh on a second thread
void ScaleMesh(Mesh mesh, float scale)
{
//Get the vertices of a mesh
var vertices = mesh.vertices;
//Run the action on a new thread
Loom.RunAsync(()=>{
//Loop through the vertices
for(var i = 0; i < vertices.Length; i++)
{
//Scale the vertex
vertices <em>= vertices _* scale;_</em>
}
//Run some code on the main thread
//to update the mesh
Loom.QueueOnMainThread(()=>{
//Set the vertices
mesh.vertices = vertices;
//Recalculate the bounds
mesh.RecalculateBounds();
});
});
}
The download for the package is in the article. Basically using QueueOnMainThread passes the closure to be run on the main thread as soon as possible or after a specified delay.
Uncomplicated and efficient:
using System.Collections.Generic;
using System.Threading;
using System;
using UnityEngine;
public class Dispatcher : MonoBehaviour
{
public static void RunAsync(Action action) {
ThreadPool.QueueUserWorkItem(o => action());
}
public static void RunAsync(Action<object> action, object state) {
ThreadPool.QueueUserWorkItem(o => action(o), state);
}
public static void RunOnMainThread(Action action)
{
lock(_backlog) {
_backlog.Add(action);
_queued = true;
}
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Initialize()
{
if(_instance == null) {
_instance = new GameObject("Dispatcher").AddComponent<Dispatcher>();
DontDestroyOnLoad(_instance.gameObject);
}
}
private void Update()
{
if(_queued)
{
lock(_backlog) {
var tmp = _actions;
_actions = _backlog;
_backlog = tmp;
_queued = false;
}
foreach(var action in _actions)
action();
_actions.Clear();
}
}
static Dispatcher _instance;
static volatile bool _queued = false;
static List<Action> _backlog = new List<Action>(8);
static List<Action> _actions = new List<Action>(8);
}
If anyone is interested I reworked the Loom Class from @whydoidoit 's Unity Gems link to an EditorWindow. This way you can easily thread long processes without locking up the editor window.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using System.Threading;
using System;
using System.IO;
using System.Linq;
public class LoomEditorWindow : EditorWindow {
public int maxThreads = 8;
private int numThreads;
private int _count;
private bool m_HasLoaded = false;
private List<Action> _actions = new List<Action>();
private List<DelayedQueueItem> _delayed = new List<DelayedQueueItem>();
private List<DelayedQueueItem> _currentDelayed = new List<DelayedQueueItem>();
private List<Action> _currentActions = new List<Action>();
public struct DelayedQueueItem
{
public float time;
public Action action;
}
protected void QueueOnMainThread(Action action)
{
QueueOnMainThread( action, 0f);
}
protected void QueueOnMainThread(Action action, float time)
{
if(time != 0)
{
lock(_delayed)
{
_delayed.Add(new DelayedQueueItem { time = Time.time + time, action = action});
}
}
else
{
lock (_actions)
{
_actions.Add(action);
}
}
}
protected Thread RunAsync(Action a)
{
while(numThreads >= maxThreads)
{
Thread.Sleep(1);
}
Interlocked.Increment(ref numThreads);
ThreadPool.QueueUserWorkItem(RunAction, a);
return null;
}
private void RunAction(object action)
{
try
{
((Action)action)();
}
catch
{
}
finally
{
Interlocked.Decrement(ref numThreads);
}
}
protected virtual void Start()
{
m_HasLoaded = true;
}
protected virtual void Update()
{
if(m_HasLoaded == false)
Start();
lock (_actions)
{
_currentActions.Clear();
_currentActions.AddRange(_actions);
_actions.Clear();
}
foreach(var a in _currentActions)
{
a();
}
lock(_delayed)
{
_currentDelayed.Clear();
_currentDelayed.AddRange(_delayed.Where(d=>d.time <= Time.time));
foreach(var item in _currentDelayed)
_delayed.Remove(item);
}
foreach(var delayed in _currentDelayed)
{
delayed.action();
}
}
}
You simply need to save current SynchronizationContext
in main thread in a variable and then you can call Post
on it with your function in other threads, e.g.:
`
var mainThreadContext = System.Threading.SynchronizationContext.Current;
mainThreadContext.Post(_ => MainThreadFun(), null);
`
here’s a solution that works without Coroutines and Loom. If it’s unimportant enough that it can wait 1 frame, just queue your data into a list for processing then use it like so:
List<string> messageQueue = new List<string>();
//your update function...
private void Update()
{
while(messageQueue.Count > 0)
{
HandleMessage(messageQueue[0]);
messageQueue.RemoveAt(0);
}
}
void HandleMessage(string msg) {
//do whatever...
}
Simply add your data to messageQueue in whatever form (I used string) and it’ll be processed on the main thread next frame.