Last active
January 11, 2023 11:06
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
Hi, I'm a little bit confused, what does this script do? Thanks!
is something like:
(data.AsyncWaitHandle as ManualResetEvent).Set();
missed at line 84?
Yes you are both right, I am sorry, fixed.
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.
/*
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
Won't it deadlock the invoking thread, since you never call Set() on AsyncWaitHandle?