SetActive() can only be called from the main thread - c#

I am stuck with this problem for 3 days, I did a lot of research, but couldn't find any answer, Here is a brief explanation of what is happening, trying to work with Firebase Database and Authentication with Unity3D, Here are the steps:
First user signs in and if it's successful, it will fetch user's data from Database and then Authentication panel should be deactivated and User's panel activated.
It Gives me this error when trying to SetActive panel.
SetActive can only be called from the main thread.
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, instead move initialization code to the Awake or Start function.
UnityEngine.GameObject:SetActive(Boolean)
public void SignInWithEmail()
{
auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
DatabaseReference.GetValueAsync().ContinueWith(task => {
//here after successful signing in, it gets the data from the Database
//and after that it should activate the user panel
//and deactivate the authentication panel
//HERE IS THE PROBLEM
userPanel.SetActive(true);
authPanel.SetActive(false);
}
}
}
I'm not trying to load another scene or anything else.
If needed I can provide more information

So my answer is very similar to the accepted answer from Milod's, but a little different, as it took me a while to wrap my head around his, even though his still works.
The Issue:
Normally, all your code runs on a single thread in Unity, since Unity is single-threaded,
however when working with APIs like Firebase, which require callbacks, the callback functions will be handled by a new thread.
This can lead to race-conditions, especially on a single-threaded engine like Unity.
The solution (from Unity):
Starting from Unity 2017.X, unity now requires changes to UI components to be run on the Main thread (i.e. the first thread that was started with Unity).
What is impacted ?:
Mainly calls that modify the UI like...
gameObject.SetActive(true); // (or false)
textObject.Text = "some string" // (from UnityEngine.UI)
How this relates to your code:
public void SignInWithEmail() {
// auth.SignInWithEmailAndPasswordAsyn() is run on the local thread,
// ...so no issues here
auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
// .ContinueWith() is an asynchronous call
// ...to the lambda function defined within the task=> { }
// and most importantly, it will be run on a different thread, hence the issue
DatabaseReference.GetValueAsync().ContinueWith(task => {
//HERE IS THE PROBLEM
userPanel.SetActive(true);
authPanel.SetActive(false);
}
}
}
Suggested Solution:
For those calls which require callback functions, like...
DatabaseReference.GetValueAsync()
...you can...
send them to a function which is set up to run on that initial thread.
...and which uses a queue to ensure that they will be run in the order that they were added.
...and using the singleton pattern, in the way advised by the Unity team.
Actual solution
Place the code below into your scene on a gameObject that will always be enabled, so that you have a worker that...
always runs on the local thread
can be sent those callback functions to be run on the local thread.
using System;
using System.Collections.Generic;
using UnityEngine;
internal class UnityMainThread : MonoBehaviour
{
internal static UnityMainThread wkr;
Queue<Action> jobs = new Queue<Action>();
void Awake() {
wkr = this;
}
void Update() {
while (jobs.Count > 0)
jobs.Dequeue().Invoke();
}
internal void AddJob(Action newJob) {
jobs.Enqueue(newJob);
}
}
Now from your code, you can simply call...
UnityMainThread.wkr.AddJob();
...so that your code remains easy to read (and manage), as shown below...
public void SignInWithEmail() {
auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
DatabaseReference.GetValueAsync().ContinueWith(task => {
UnityMainThread.wkr.AddJob(() => {
// Will run on main thread, hence issue is solved
userPanel.SetActive(true);
authPanel.SetActive(false);
})
}
}
}

So basically UI elements need to be modified in Main thread, and I found this script and it will execute your function in Main thread, just put your function in a Coroutine and Enqueue it to the script(UnityMainThreadDispatcher). (You need an object in the scene and add the MainThreadDispathcer script to it)
Here's how my Function looked:
public void SignInWithEmail()
{
auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
DatabaseReference.GetValueAsync().ContinueWith(task => {
//Here's the fix
UnityMainThreadDispatcher.Instance().Enqueue(ShowUserPanel());
}
}
}
public IEnumerator ShowUserPanel()
{
uiController.userPanel.panel.SetActive(true);
uiController.authPanel.SetActive(false);
yield return null;
}
This is the script to run it in Main Thead
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public class UnityMainThreadDispatcher : MonoBehaviour {
private static readonly Queue<Action> _executionQueue = new Queue<Action>();
/// <summary>
/// Locks the queue and adds the IEnumerator to the queue
/// </summary>
/// <param name="action">IEnumerator function that will be executed from the main thread.</param>
public void Enqueue(IEnumerator action) {
lock (_executionQueue) {
_executionQueue.Enqueue (() => {
StartCoroutine (action);
});
}
}
/// <summary>
/// Locks the queue and adds the Action to the queue
/// </summary>
/// <param name="action">function that will be executed from the main thread.</param>
public void Enqueue(Action action)
{
Enqueue(ActionWrapper(action));
}
IEnumerator ActionWrapper(Action a)
{
a();
yield return null;
}
private static UnityMainThreadDispatcher _instance = null;
public static bool Exists() {
return _instance != null;
}
public static UnityMainThreadDispatcher Instance() {
if (!Exists ()) {
throw new Exception ("UnityMainThreadDispatcher could not find the UnityMainThreadDispatcher object. Please ensure you have added the MainThreadExecutor Prefab to your scene.");
}
return _instance;
}
void Awake() {
if (_instance == null) {
_instance = this;
DontDestroyOnLoad(this.gameObject);
}
}
public void Update() {
lock(_executionQueue) {
while (_executionQueue.Count > 0) {
_executionQueue.Dequeue().Invoke();
}
}
}
void OnDestroy() {
_instance = null;
}
}

Note that Firebase has now a nice ContinueWithOnMainThread extension method that solves this problem more elegantly than the other suggested answers:
using Firebase.Extensions;
public void SignInWithEmail() {
// This code runs on the caller's thread.
auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
// This code runs on an arbitrary thread.
DatabaseReference.GetValueAsync().ContinueWithOnMainThread(task => {
// This code runs on the Main thread. No problem.
userPanel.SetActive(true);
authPanel.SetActive(false);
}
}
}```

Related

How to lock a Coroutine?

I have a custom scene manager, which apart from loading scenes calls some events for me. I want it to fully load a scene, before it starts loading another one, so I added a lock. I'm using Monitor because I want to enter the lock in OnLoadScene() and exit it in LoadSceneAsync() and this wouldn't be possible with the lock() {} syntax, because LoadSceneAsync() is called as a Coroutine (asynchronously). I found the Monitor syntax here: C# manual lock/unlock.
Here is the code:
public class CustomSceneManager : Singleton<CustomSceneManager>
{
public delegate void SceneChange(string sceneName);
public event SceneChange LoadScene;
public event SceneChange UnloadScene;
private static readonly object LoadSceneLock = new object();
private IEnumerator LoadSceneAsync(string sceneName)
{
var asyncLoadLevel = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
while (!asyncLoadLevel.isDone)
{
yield return null;
}
Debug.Log($"Finished loading: {sceneName}");
LoadScene?.Invoke(sceneName);
Monitor.Exit(LoadSceneLock); // exit lock
}
public void OnLoadScene(string newSceneName)
{
Monitor.Enter(LoadSceneLock); // enter lock
Debug.Log($"Started loading: {newSceneName}");
UnloadScene?.Invoke(newSceneName);
StartCoroutine(LoadSceneAsync(newSceneName));
}
}
The issue is that it doesn't work as I expect it to. This is how my logs look:
Started loading: scene_1
Started loading: scene_2
Finished loading: scene_1
Finished loading: scene_2
Am I using the lock incorrectly?

two-state water tank modeling

In an online test question, I was asked to complete the code for a multithread water tank monitor. When one tries to Empty() an empty WaterTank, one has to wait until it's filled, and the program has to block other threads from calling the same method when it isEmpty(). Similarly, to Fill() a filled WaterTank, one has to wait until it isEmpty(), and the program has to block other threads from the same method again until a state changex`.
I was given the code defining the WaterTank class, which consisted only of a private boolean empty having a public getter and setter,
public class WaterTank
{
private bool empty = true;
public virtual bool IsEmpty()
{
return empty;
}
public virtual void SetEmpty (bool b)
{
// Something is wrong if b == empty
empty = b;
Console.WriteLine(empty ? "Empty":"Full");
}
}
and the code of the WaterTankMonitor class to be completed. The given code for each method contained an empty while loop and a call to SetEmpty().
using System;
public class WaterTankMonitor
{
/// The water tank that this class monitors.
private WaterTank tank;
public WaterTankMonitor(WaterTank tank)
{
this.tank = tank;
}
/// Empty the water tank.
public void Empty()
{
while (tank.IsEmpty())
{
}
tank.SetEmpty (true);
}
/// Fill the water tank.
public void Fill()
{
while (!tank.IsEmpty())
{
}
tank.SetEmpty(false);
}
}
I tried using lock in a similar way as the linked code, but I added readonly as that's recommended. I don't know why the system said my response was wrong.
public class WaterTankMonitor
{
private WaterTank tank;
private readonly object o = new object();
public WaterTankMonitor(WaterTank tank)
{
this.tank = tank;
}
public void Empty()
{
lock(o)
{
while (tank.IsEmpty())
{
}
tank.SetEmpty(true);
}
}
public void Fill()
{
lock(o)
{
while (!tank.IsEmpty())
{
}
tank.SetEmpty(false);
}
}
}
Related Java code.
(Edited)
I am supposed to program the WaterTankMonitor class, and I don't have the right to change the WaterTank class and the following test code.
WaterTank tank = new WaterTank();
WaterTankMonitor monitor = new WaterTankMonitor(tank);
monitor.Fill();
monitor.Empty();
Think: What happens with the lock+while loop? When is the lock released? If one thread tries emptying the tank and finds it empty, can another thread fill it in the meantime?
This should lead you to the root of the problem.
As a side note, this:
while (tank.IsEmpty())
is just brutal.
These things are better done with i.e. condition variables.. in "put the thread to sleep until X changes" style. But of course, not every platform provides condition variables, and using them is a bit more complicated than busy-looping.

Main thread coroutine using Queue<Action>

I am having a little trouble when i get some data from websockets and try to display it through coroutines.
First, I have a classA attached to an object that opens the websocket and displays the data I receive:
public class ClassA : MonoBehaviour {
...
public IEnumerator ConnectWebSocket(url)
{
// in the websocket class, start the websocket connection, which
// will return data through a callback inside the return string
WebSocketClass.WebSocketStart(url, delegate(string result)
{
// receive websocket data and call the functions that displays it
WebSocketData(result);
});
// wait for the socket connection
while (WebSocketClass.WebSocketGetState() == WebSocketSharp.WebSocketState.CONNECTING)
{
yield return 0;
}
if (WebSocketClass.WebSocketGetState() == WebSocketSharp.WebSocketState.OPEN)
{
break;
}
...
}
// function that gets websocket data and starts couroutine to display it
public void WebSocketData(string data)
{
StartCoroutine(DisplayMessage(data));
}
}
But Unity complains with the next error:
StartCoroutine_Auto can only be called
from the main thread. 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,
instead move initialization code to
the Awake or Start function.
I searched in the unity forum and found this solution:
public class ClassA : MonoBehaviour {
...
public IEnumerator ConnectWebSocket(url)
{
// in the websocket class, start the websocket connection, which
// will return data through a callback inside the return string
WebSocketClass.WebSocketStart(url, delegate(string result)
{
// receive websocket data and call the functions that displays it
WebSocketData(result);
});
// wait for the socket connection
while (WebSocketClass.WebSocketGetState() == WebSocketSharp.WebSocketState.CONNECTING)
{
yield return 0;
}
if (WebSocketClass.WebSocketGetState() == WebSocketSharp.WebSocketState.OPEN)
{
break;
}
...
}
// function that gets websocket data and starts couroutine to display it
public void WebSocketData(string data)
{
DoOnMainThread.ExecuteOnMainThread.Enqueue(() => { StartCoroutine(DisplayMessage(data)); });
}
}
// class to manage the websocket data display inside the main thread
public class DoOnMainThread : MonoBehaviour
{
public readonly static Queue<Action> ExecuteOnMainThread = new Queue<Action>();
public virtual void Update()
{
// dispatch stuff on main thread
while (ExecuteOnMainThread.Count > 0)
{
ExecuteOnMainThread.Dequeue().Invoke();
}
}
}
And it works! the problem is that even though I wrote the two classes in the same cs file and attached to an object, when I change the scene, return to that scene, and receive any data from the websocket, the next error is displayed:
MissingReferenceException: The object
of type 'ClassA' has been destroyed
but you are still trying to access it.
Your script should either check if it
is null or you should not destroy the
object.
UnityEngine.MonoBehaviour.StartCoroutine
(IEnumerator routine) (at
C:/BuildAgent/work/d63dfc6385190b60/artifacts/EditorGenerated/UnityEngineMonoBehaviour.cs:62)
I tried not destroying the object when a new scene is loaded, as the documentation says:
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}
But the error still appears.
The weird thing is that although there is an error, the data received from the websocket is displayed without any problem.
Does someone know how to avoid this problem? Any way to trigger a coroutine inside the main thread without using a second class? Or other solution to avoid this error?
Thanks!
I found the problem:
public readonly static Queue<Action> ExecuteOnMainThread = new Queue<Action>();
It is static, so it becomes a problem when a public class is instantiated and generates another ExecuteOnMainThread.
So just deleted "static" and made it destroy and generate itself every time ClassA is created by Unity.
Now it works like a charm : )

ManualResetEvent WaitOne blocks the owner Thread of my CollectionView

I've written a WPF WizardFramework which performs some actions in the background using some BackgroundWorker. While processing it can happen that I have to update an ObservableCollection which is bound to my UI.
For this case I've written a ThreadableObservableCollection, which provides threadsafe methods for Insert, Remove and RemoveAt. Though I'm using .NET 4.5 I was not able to get BindingOperations.EnableCollectionSynchronization working without many other invalid access exceptions. My Collection looks like:
public class ThreadableObservableCollection<T> : ObservableCollection<T>
{
private readonly Dispatcher _dispatcher;
public ThreadableObservableCollection()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
public void ThreadsafeInsert(int pos, T item, Action callback)
{
if (_dispatcher.CheckAccess())
{
Insert(pos, item);
callback();
}
else
{
_dispatcher.Invoke(() =>
{
Insert(pos, item);
callback();
});
}
}
[..]
}
This is working as expected, while I am using the wizard in my application. Now I'm using NUnit to write some integrationtests for the application.
There's a listener which waits for the WizardViewModel to finish it's work and looking for some pages which are injected in the Steps-Collection. After the asyncrone work is done I can use Validate to check the viewmodel state.
Unfortunately I'm using a ManualResetEvent to wait for the wizard to close. This looks like following:
public class WizardValidator : IValidator, IDisposable
{
private WizardViewModel _dialog;
private readonly ManualResetEvent _dialogClosed = new ManualResetEvent(false);
[..]
public void ListenTo(WizardViewModel dialog)
{
_dialog = dialog;
dialog.RequestClose += (sender, args) => _dialogClosed.Set();
dialog.StepsDefaultView.CurrentChanged += StepsDefaultViewOnCurrentChanged;
_dialogClosed.WaitOne();
}
[..]
}
Now there's a problem:
While the Application is running the UI Thread is not blocked, the Collection can be updated without any problems. But in my testcases the "main" Thread where I initialize the ViewModel (and because of that the Collections) is an AppDomainThread which is blocked by the testcode. Now my ThreadsafeInsert wants to update the collection but cannot use the AppDomain Thread.
But I have to wait for the wizard to finish, how can I solve this kind of deadlock? Or is there a more elegant solution for this one?
edit:
I worked around this problem with a check if there's a user interface, and only then I invoke on the Application-Thread, otherwise I change the collection intentionally on another thread. This does not prevent the exception, but it is not recognized from the test... the items are inserted nevertheless, only the NotifyCollectionChanged-Handler is not called (which is only used in the UI anyway).
if (Application.Current != null)
{
Application.Current.Dispatcher.Invoke(() =>
{
Steps.Insert(pos, step);
stepsView.MoveCurrentTo(step);
});
}
else
{
new Action(() => Steps.Insert(pos, step)).BeginInvoke(ar => stepsView.MoveCurrentToPosition(pos), null);
}
This is an ugly workaround and I am still interested in a clean solution.
Is there a way to use an alternate Dispatcher to create (e.g.) the whole ViewModel and use this to change my collection?
As I see the main problem that main thread is blocked and other operations are trying to be executed in main thread too? What about not to block main thread, like this:
// helper functions
public void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
public object ExitFrame(object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}
// in your code:
while(!_dialogClosed.WaitOne(200))
DoEvents();
If it will not help then I guess need to try some SynchronisationContext workarounds.
I think the problems boil down to the fact that you create ObservableCollection that is tied to Dispatcher object.
Involving Dispatcher object directly is almost never good idea(as you just witnessed). Instead I would suggest you to see how others have implemented ThreadSafeObservableCollection. This is a little example I put together, it should illustrate the point:
public class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
private readonly object _lock = new object();
public ThreadSafeObservableCollection()
{
BindingOperations.CollectionRegistering += CollectionRegistering;
}
protected override void InsertItem(int index, T item)
{
lock (_lock)
{
base.InsertItem(index, item);
}
}
private void CollectionRegistering(object sender, CollectionRegisteringEventArgs e)
{
if (e.Collection == this)
BindingOperations.EnableCollectionSynchronization(this, _lock);
}
}

Invoke/Call an even in a different thread

I have to make a Instant Messenger server in C#. The server is a ConsoleApplication project. And I want to make a server that runs in 3 threads. I will explain in the code below.
The question is how can I invoke a function from a separate thread, or make an event on a separate thread?
This is the main server class.
public class GiNetServer
{
public void Start()
{
netServer = new NetServer();
msgHandler = new NetMsgHandler();
netServer.NewNetMsg += msgHandler.HandleMsg;
Work();
}
private void Work()
{
while(true) //This runs in the MainThread
sleep(1);
}
}
The NetServer class creates 2 Threads: acceptClientsThread and receiveMessagesThread.
The receive thread calls the NewNetMsg.
public class NetServer
{
public event NewNetMsgEventHandler NewNetMsg;
public NetServer()
{
acceptClientsThread = new Thread(ListenForClients);
receiveMessageThread = new Thread(Receive);
//and of course starts them here...
}
private void Receive()
{
while(true)
{
Heartbeat();
}
}
private void Heartbeat()
{
foreach(netClient in clientsList)
{
if (netClient.DataAvalible)
{
netClient.Recive();
}
if (!netClient.IsBufferEmpty())
{
nextMsg = netClient.NextMessage();
if (nextMsg != null)
NewNetMsg(netClient, nextMsg); //Call the event!
}
}
}
}
How can I make the msgHandler.HandleMsg function run in a separate thread or in the MainThread?
Like this, HandleMsg runs in the receiveMessagesThread.
The code above is pseudocode-ish. If there is anything ambigous please let me know.
There are a lot of different ways to move the HandleMsg call onto a different thread, depending on what your requirements are. The simplest way would be to raise the NewNetMsg event on a different thread using the ThreadPool:
ThreadPool.QueueUserWorkItem(s => NewNetMsg(netClient, nextMsg));
You could also use the Task Parallel Library (TPL). You could also add the event to a ConcurrentQueue that is processed by a dedicated background thread. And there are more options. Without more details it is impossible to give a more specific recommendation.

Categories

Resources