- Home /
How do I invoke functions on the main thread?
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?
Before doing any of that you should read this:
https://stackoverflow.com/a/54184457/294884
threading + frame based system causes vast confusion
Answer by MadDave · Aug 22, 2012 at 10:16 AM
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.
Could you post a solution to this, as the question is marked as solved, please?
Unity currently uses $$anonymous$$ono 2.6, which is approximately .NET 3.5. I don't know if it has System.Windows.Threading though.
I made a quick library that describes exactly what you're talking about. https://github.com/PimDeWitte/Unity$$anonymous$$ainThreadDispatcher
Answer by pdwitte · Apr 02, 2016 at 05:24 AM
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 https://github.com/PimDeWitte/UnityMainThreadDispatcher and start using it if you'd like.
Thank you for this! Just wondering; does the function queued always have to be an IEnumerator
, or can we alter that aspect?
Answer by Curious-George · Oct 07, 2017 at 08:45 PM
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);
}
Answer by whydoidoit · Apr 23, 2013 at 01:07 PM
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[i] = vertices[i] * scale;
}
//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.
Ok this is easy to get and works if you start perfor$$anonymous$$g your operations from the main thread itself. (as i understood so far) But if you have a function in your class, that gets called by an other thread via e.g. .net Remoting, this method will fail...*
public void NotifyFinished(int status)
{
Debug.Log("Notify");
try
{
if (status == (int)LevelStatusCode.O$$anonymous$$)
{
Loom.QueueOn$$anonymous$$ainThread(() =>
{
PresentNameInputController();
});
}
}
catch (Exception e)
{
Debug.LogError(e.ToString());
}
}
Do you possibly know how to solve that problem?
Constructors and field initializers will be executed from the loading thread when loading a scene. Don't use this function in the constructor or field initializers, ins$$anonymous$$d move initialization code to the Awake or Start function. I get that ArgumentException in Loom:Initialize()get_isPlaying can only be called from the main thread.
You have to initialize the Loom object first from the Unity thread. So just add this line to a Start method which runs before your thread:
var tmp = Loom.Current;
This line will invoke the Initialize() function of Loom. Note this kine will preduce a warning on the tmp variable. The problem is that the Initialize method is private and all you can do is accessing the Current property ;)
Alternatives are:
edit the Loom script and make the Initialize method public, so you can call it from Start
Do something useless with the Loom-object.
That's a line which doesn't produce a warning:
Loom.Current.GetComponent<Loom>();
THAN$$anonymous$$S, that helped!
It is working with
void Start()
{
var tmp = Loom.Current;
...
}
//Function called from other Thread
public void NotifyFinished(int status)
{
Debug.Log("Notify");
try
{
if (status == (int)LevelStatusCode.O$$anonymous$$)
{
Loom.QueueOn$$anonymous$$ainThread(() =>
{
PresentNameInputController();
});
}
}
catch (Exception e)
{
Debug.LogError(e.ToString());
}
}
Great work with this!!
Just a note - if you aren't using the closure you can just do:
Loom.QueueOn$$anonymous$$ainThread(PresentNameInputController);
Hi. I found same problem. I need to call function which is in another thread.
What is Loom? how to make it work?
Your link ( Unity Gems Threading Article) does not work anymore.
Please let me know.
Answer by PAEvenson · Aug 29, 2013 at 02:22 PM
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();
}
}
}