Skip to content

Instantly share code, notes, and snippets.

@JakubNei
Last active January 11, 2023 11:06
Show Gist options
  • Save JakubNei/90bf21be3fdc4829e631 to your computer and use it in GitHub Desktop.
Save JakubNei/90bf21be3fdc4829e631 to your computer and use it in GitHub Desktop.
Implementation of ISynchronizeInvoke for Unity3D game engine. Can be used to invoke anything on main Unity thread. ISynchronizeInvoke is used extensively in .NET forms, it's is elegant and quite useful in Unity as well. I implemented it so i can use it with System.IO.FileSystemWatcher.SynchronizingObject.
/*
Implementation of ISynchronizeInvoke for Unity3D game engine.
Can be used to invoke anything on main Unity thread.
ISynchronizeInvoke is used extensively in .NET forms, it's is elegant and quite useful in Unity as well.
I implemented it so i can use it with System.IO.FileSystemWatcher.SynchronizingObject.
help from: http://www.codeproject.com/Articles/12082/A-DelegateQueue-Class
example usage: https://gist.github.com/aeroson/90bf21be3fdc4829e631
version: aeroson 2017-07-13 (author yyyy-MM-dd)
license: WTFPL (http://www.wtfpl.net/)
*/
using System.Threading;
using System.ComponentModel;
using System.Collections.Generic;
using System;
public class DeferredSynchronizeInvoke : ISynchronizeInvoke
{
private Queue<AsyncResult> toExecute = new Queue<AsyncResult>();
private Thread mainThread;
public bool InvokeRequired { get { return mainThread.ManagedThreadId != Thread.CurrentThread.ManagedThreadId; } }
public DeferredSynchronizeInvoke()
{
mainThread = Thread.CurrentThread;
}
public IAsyncResult BeginInvoke(Delegate method, object[] args)
{
var asyncResult = new AsyncResult()
{
method = method,
args = args,
IsCompleted = false,
manualResetEvent = new ManualResetEvent(false),
};
if (InvokeRequired)
{
lock (toExecute)
toExecute.Enqueue(asyncResult);
}
else
{
asyncResult.Invoke();
asyncResult.CompletedSynchronously = true;
}
return asyncResult;
}
public object EndInvoke(IAsyncResult result)
{
if (!result.IsCompleted)
result.AsyncWaitHandle.WaitOne();
return result.AsyncState;
}
public object Invoke(Delegate method, object[] args)
{
if (InvokeRequired)
{
var asyncResult = BeginInvoke(method, args);
return EndInvoke(asyncResult);
}
else
{
return method.DynamicInvoke(args);
}
}
public void ProcessQueue()
{
if (Thread.CurrentThread != mainThread)
throw new Exception(
"must be called from the same thread it was created on " +
"(created on thread id: " + mainThread.ManagedThreadId + ", called from thread id: " + Thread.CurrentThread.ManagedThreadId
);
AsyncResult data = null;
while (true)
{
lock (toExecute)
{
if (toExecute.Count == 0)
break;
data = toExecute.Dequeue();
}
data.Invoke();
}
}
private class AsyncResult : IAsyncResult
{
public Delegate method;
public object[] args;
public bool IsCompleted { get; set; }
public WaitHandle AsyncWaitHandle { get { return manualResetEvent; } }
public ManualResetEvent manualResetEvent;
public object AsyncState { get; set; }
public bool CompletedSynchronously { get; set; }
public void Invoke()
{
AsyncState = method.DynamicInvoke(args);
IsCompleted = true;
manualResetEvent.Set();
}
}
}
using UnityEngine;
using System.Threading;
public class UnitySynchronizeInvokeExample : MonoBehaviour
{
private DeferredSynchronizeInvoke synchronizeInvoke;
private void Start()
{
synchronizeInvoke = new DeferredSynchronizeInvoke();
new Thread(ThreadMain).Start();
}
private void ThreadMain()
{
while (true)
{
var retObj = synchronizeInvoke.Invoke((System.Func<string>)(() =>
{
this.transform.localScale = Vector3.one * Random.Range(1.0f, 10.0f);
return this.gameObject.name;
}), null);
Debug.Log("Waited for the end of synchronizeInvoke and it synchronously returned me: " + (retObj as string));
Thread.Sleep(1 * 1000);
}
}
private void Update()
{
synchronizeInvoke.ProcessQueue();
}
}
@m4yers
Copy link

m4yers commented May 10, 2016

Won't it deadlock the invoking thread, since you never call Set() on AsyncWaitHandle?

@z3t0
Copy link

z3t0 commented Jul 3, 2016

Hi, I'm a little bit confused, what does this script do? Thanks!

@hojjatjafary
Copy link

hojjatjafary commented Sep 5, 2016

is something like:
(data.AsyncWaitHandle as ManualResetEvent).Set();
missed at line 84?

@JakubNei
Copy link
Author

Yes you are both right, I am sorry, fixed.

@tigrouind
Copy link

When running this in Unity editor, when will the thread started in Start() finish execution ?
AFAIK, it will run forever, even if stop button is pressed in Unity editor.

@hdalaq
Copy link

hdalaq commented Feb 10, 2021

/*
	Implementation of ISynchronizeInvoke for Unity3D game engine.
	Can be used to invoke anything on main Unity thread.
	ISynchronizeInvoke is used extensively in .NET forms, it's is elegant and quite useful in Unity as well.
	I implemented it so i can use it with System.IO.FileSystemWatcher.SynchronizingObject.

	help from: http://www.codeproject.com/Articles/12082/A-DelegateQueue-Class
	example usage: https://gist.github.com/aeroson/90bf21be3fdc4829e631

	This version has been updated so that the Queue and it's process is global accross all DeferredSynchronizeInvoke
	instances, this allows it to be used from anywhere.

	version: hdalaq 2021-02-09 (author yyyy-MM-dd)
	license: WTFPL (http://www.wtfpl.net/)
*/

using System.Threading;
using System.ComponentModel;
using System.Collections.Generic;
using System;
using UnityEditor;

public class DeferredSynchronizeInvoke : ISynchronizeInvoke
{
    [InitializeOnLoadMethod]
    private static void Init()
    {
        mainThread = Thread.CurrentThread;
        EditorApplication.update += ProcessQueue;
    }

    private static Thread mainThread;
    private static readonly Queue<AsyncResult> ToExecute = new Queue<AsyncResult>();

    private static void ProcessQueue()
    {
        if (Thread.CurrentThread != mainThread)
            throw new Exception(
                "must be called from the same thread it was created on " +
                "(created on thread id: " + mainThread.ManagedThreadId + ", called from thread id: " +
                Thread.CurrentThread.ManagedThreadId
            );

        AsyncResult data = null;
        while (true)
        {
            lock (ToExecute)
            {
                if (ToExecute.Count == 0)
                {
                    break;
                }

                data = ToExecute.Dequeue();
            }

            data.Invoke();
        }
    }

    public bool InvokeRequired => mainThread.ManagedThreadId != Thread.CurrentThread.ManagedThreadId;

    public IAsyncResult BeginInvoke(Delegate method, object[] args)
    {
        var asyncResult = new AsyncResult()
                          {
                              method           = method,
                              args             = args,
                              IsCompleted      = false,
                              manualResetEvent = new ManualResetEvent(false),
                              invokingThread = Thread.CurrentThread
                          };

        if (mainThread.ManagedThreadId != asyncResult.invokingThread.ManagedThreadId)
        {
            lock (ToExecute)
            {
                ToExecute.Enqueue(asyncResult);
            }
        }
        else
        {
            asyncResult.Invoke();
            asyncResult.CompletedSynchronously = true;
        }

        return asyncResult;
    }

    public object EndInvoke(IAsyncResult result)
    {
        if (!result.IsCompleted)
            result.AsyncWaitHandle.WaitOne();

        return result.AsyncState;
    }

    public object Invoke(Delegate method, object[] args)
    {
        if (InvokeRequired)
        {
            var asyncResult = BeginInvoke(method, args);
            return EndInvoke(asyncResult);
        }
        else
        {
            return method.DynamicInvoke(args);
        }
    }

    private class AsyncResult : IAsyncResult
    {
        public Delegate method;
        public object[] args;
        public bool IsCompleted { get; set; }

        public WaitHandle AsyncWaitHandle => manualResetEvent;

        public ManualResetEvent manualResetEvent;
        public Thread invokingThread;

        public object AsyncState { get; set; }
        public bool CompletedSynchronously { get; set; }

        public void Invoke()
        {
            AsyncState  = method.DynamicInvoke(args);
            IsCompleted = true;
            manualResetEvent.Set();
        }
    }
}

I updated this to make the "ToExecute" Queue and the "ProcessQueue()" Method global. So you don't have to worry about calling it from a Monobehaviour update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment