Execute method outside Task.run - c#

Call a method outside of Task.Run(). I have this code
private void StartListening(int numPort, int tasknumber)
{
udpClients[tasknumber] = new UdpClient();
udpClients[tasknumber].Client.Bind(new IPEndPoint(IPAddress.Any, numPort));
iPEndPoints[tasknumber] = new IPEndPoint(0, 0);
Task.Run(() =>
{
while (true)
{
AddUniqueRoomList(iPEndPoints[tasknumber].Address.ToString(), Encoding.UTF8.GetString(udpClients[tasknumber].Receive(ref iPEndPoints[tasknumber])));
}
});
}
The code waits for a broadcast message then send a string to AddUniqueRoomList. What I want is just send a message to AddUniqueRoomList method without including it in Task.Run. AddUniqueRoomList method creates a UI button which causes an error if it's inside a task.run() cause unity doesn't do well with multithreading.

As in the comments you can use an often referred to as "dispatcher" in order to let any Action be executed in the Unity main thread.
The simplest way is actually to directly use a thread-save ConcurrentQueue<Action> which doesn't require locks like
public class MainThreadDispatcher : MonoBehaviour
{
// Singleton pattern
private static MainThreadDispatcher _instance;
// This thread-save queue stores Actions to be executed in the main thread
// any thread can add Actions to it via the ExecuteInUpdate method
// then the existing actions will be executed in the Update loop
// in the same order they came in
private readonly ConcurrentQueue<Action> actions = new ConcurrentQueue<Action>();
// This may be called by threads
public static void ExecuteInUpdate(Action action)
{
if(!_instance)
{
Debug.LogError("You can talk but nobody is listening../nNo MainThreadDispatcher in scene!");
return;
}
// Add the action as new entry at the end of the queue
_instance.actions.Enqueue(action);
}
// Initialize the singleton
private void Awake()
{
if(_instance && _instance != this)
{
// an instance already exists so destroy this one
Destroy(gameObject);
return;
}
_instance = this;
DontDestroyOnLoad(this);
}
// This will be executed when the game is started
// AFTER all Awake calls
// See https://docs.unity3d.com/ScriptReference/RuntimeInitializeOnLoadMethodAttribute.html
[RuntimeInitializeOnLoadMethod]
private static void Initialize()
{
// If the instance exists already everything is fine
if(_instance) return;
// otherwise create it lazy
// This adds a new GameObject to the scene with the MainThreadDispatcher
// component attached -> this will call Awake automatically
new GameObject("MainThreadDispatcher", typeof(MainThreadDispatcher));
}
// Executed in the Unity main thread
private void Update()
{
// Every frame check if any actions are enqueued to be executed
while(actions.TryDequeue(out car action)
{
action?.Invoke();
}
}
}
Put this script on any active object in the scene so it's Update is allways called.
Then in your script you wouldn't execute the method directly but rather add it to this queue by doing
private void StartListening(int numPort, int tasknumber)
{
udpClients[tasknumber] = new UdpClient();
udpClients[tasknumber].Client.Bind(new IPEndPoint(IPAddress.Any, numPort));
iPEndPoints[tasknumber] = new IPEndPoint(0, 0);
Task.Run(() =>
{
while (true)
{
var receivedString = Encoding.UTF8.GetString(udpClients[tasknumber].Receive(ref iPEndPoints[tasknumber]));
var address = iPEndPoints[tasknumber].Address.ToString();
MainThreadDispatcher.ExexuteInUpdate(
() =>
{
AddUniqueRoomList(address, receivedString);
}
);
}
});
}

Related

How to repeatedly execute a method until said otherwise

I have an EventSystem for managing my turn-based game in Unity.
public class EventSystem : MonoBehaviour
{
private static List<Action> _commandsQueue = new List<Action>();
private bool _canExecuteCommand = true;
public void AddToQueue(Action command)
{
_commandsQueue.Add(command);
}
private void StartCommandExecution()
{
_commandsQueue[0]();
_canExecuteCommand = false;
}
public void CommandExecutionComplete()
{
_canExecuteCommand = true;
}
public void PlayFirstCommandFromQueue()
{
if (_commandsQueue.Any() && _canExecuteCommand)
{
StartCommandExecution();
}
else
{
Debug.LogError("No Command In Queue");
}
}
}
How do I put a method in Update() until _canExecuteCommand is true again but only for some methods?
It is quite broad what you are trying to do but in general you would use an endless loop within a Coroutine.
You can create a generic routine which invokes any Action you pass in as parameter once a frame like e.g.
private IEnumerator InvokeEveryFrame(Action action)
{
// This looks strange but is okey in a Coroutine as long as you yield somewhere within
while(true)
{
action?.Invoke();
// This tells Unity to "pause" this routine here
// render the current frame and continue from here in the next frame
yield return null;
}
}
So all that's left is starting the routine using MonoBehaviour.StartCoroutine like e.g.
Coroutine routine = StartCoroutine(SomeParameterlessMethod);
or if you need parameters
Coroutine routine = StartCoroutine(() => SomeMethod(x, y, z));
and then at some point later stop it using MonoBehaviour.StopCoroutine and the stored Coroutine reference like e.g.
StopCoroutine(routine);
how exactly you store that reference is up to you of course up to you.

why this in a callback causes the code to stop?

After recovering my data with Firebase using a callback in GetValueAsync().ContinueWith(task..) , I would like to instantiate my prefab in order to see the list of scores for my leaderboard. But, it does nothing and I have no errors. The code simply stops in the callback UseSores as soon as it come across on a 'this' or a 'instantiate'.
public class Leaderboardmanager : MonoBehaviour
{
public GameObject rowLeardBoard;
FirebaseDB_Read read;
float positionX;
int nbRows = 10;
void Start()
{
read = (gameObject.AddComponent<FirebaseDB_Read>());
GetScorePlayer();
}
void GetScorePlayer()
{
read.GetScores(UseScores, "entries/LeaderBoard/", nbRows);
}
void UseScores(IList<FirebaseDB_Read.Score> scores)
{
Debug.Log("arrive here");
positionX = this.transform.position.y;
Debug.Log("does not arrive here");
}
}
Here is to get my data :
public class FirebaseDB_Read : MonoBehaviour
{
public class Score
{
public string UID;
public string score;
public int rank;
}
public void GetScores(Action<IList<Score>> callback, string URL_TO_SCORES, int limit)
{
DatabaseReference scoresRef = FirebaseDatabase.DefaultInstance.GetReference(URL_TO_SCORES);
scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWith(task =>
{
DataSnapshot snapshot = task.Result;
IList<Score> objectsList = new List<Score> { };
int i = 1;
foreach (var childSnapshot in snapshot.Children)
{
Score score = new Score();
score.rank = i;
score.UID = childSnapshot.Child("UID").GetValue(true).ToString();
score.score = childSnapshot.Child("score").GetValue(true).ToString();
objectsList.Add(score);
i++;
}
callback(objectsList);
});
}
}
This is an often asked problem in Unity: Because you ContinueWith on a background thread!
Unity isn't thread-safe, meaning that most of the Unity API can only be used within the Unity main thread.
Firebase offers an extension specifically for Unity: ContinueWithOnMainThread which assures that the result is handled in the Unity main thread where accessing the API is valid.
scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWithOnMainThread(task =>
{
...
});
As alternative you can use kind of a so called "main thread dispatcher" pattern and make sure that the callback is executed in the main thread on the receiver side. The advantage of this would be that the still expensive operations on your list are all executed on a background thread, not affecting the UI performance
scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWith(task =>
{
...
});
but then on receiver side in FirebaseDB_Read
private readonly ConcurrentQueue<Action> _mainThreadActions = new ConcurrentQueue<Action>();
private void Update()
{
if(_mainThreadAction.Count > 0)
{
while(_mainThreadActions.TryDequeue(out var action))
{
action?.Invoke();
}
}
}
void GetScorePlayer()
{
read.GetScores(UseScores, "entries/LeaderBoard/", nbRows);
}
void UseScores(IList<FirebaseDB_Read.Score> scores)
{
// handle this in the next main thread update
_mainThreadActions.Enqueue(() =>
{
Debug.Log("arrive here");
positionX = this.transform.position.y;
Debug.Log("does not arrive here");
}
}
which on the offside of course introduces a little overhead for checking for any new actions in Update of course. So if you plan do use multiple of such background actions make sure to implement them in one central place in order to keep the overhead limited ;)

Cant instantiate a prefab from another script

I cant instantiate a prefab from another script, it does nothing and gives me no errors. In the same "play" i can instantiate the same prefab from the same script multiple times but if i try to do it by executing the same method from another script it doesnt work.... i pass some variables and print them ( it work) but instatiation is doing nothing....please help!
I tried to solve it by:
public GameObject reference,
UnityEvents and
Ataching both scripts in one object
Script One
void OnEnable()
{
EventSystemManager.StartListening("printMessage", AddMessage);
}
public void AddMessage(string message )
{
GameObject remoteMessage = Instantiate(localMessagePrefab,
chatTransform);
remoteMessage.transform.GetChild(0).GetComponentInChildren<Text>
().text = message;
}
Script Two
public IEnumerator StartWebSocket()
{
using (ws = new WebSocket(serveradress))
{
ws.OnOpen += (sender, e) =>
onOpenHandler();
ws.OnMessage += (sender, e) =>
onMessageHandler(e.Data);
ws.OnError += (sender, e) =>
onErrorHandler(e);
ws.OnClose += (sender, e) =>
onCloseHandler(e);
ws.Connect();
for (; ; )
{
yield return null;
}
}
}
private async void onMessageHandler(string e)
{
serverMessage = JsonUtility.FromJson<serverMssg>(e);
EventSystemManager.TriggerEvent("printMessage",
serverMessage.normmsg);
await miaApi.queueAnim(serverMessage.normmsg);
}
The message is passed but instantiation do nothing....
The only thing i detect in debugging is that transform dissapears when tryng to do it from other script:
from the same script:
from another script:
Also there is a video (without sound)
The video doesnt use events but is exactly the same behavior
Video
TIA!
I don't know how the EventSystemManager works.
In case the issue is actually the async and multi-threading you could try a simple "dispatcher" for passing callbacks back to the main thread:
public class MainThreadDispatcher : MonoBehaviour
{
private static MainThreadDispatcher _instance;
public static MainThreadDispatcher Instance
{
get
{
if(!_instance)
{
_instance = new GameObject("MainThreadDispatcher").AddComponent<MainThreadDispatcher>();
DontDestroyOnLoad(_instance.gameObject);
}
return _instance;
}
}
private ConcurrentQueue<Action> actions = new ConcurrentQueue<Action>();
private void Update()
{
// work the queue in the main thread
while(actions.TryDequeue(out var action))
{
action?.Invoke();
}
}
public void Dispatch(Action action)
{
actions.Enqueue(action);
}
}
and then use it to make sure the callback is done in the main thread
private MainThreadDispatcher mainThreadDispatcher;
void OnEnable()
{
// before dispatching stuff make sure an instance reference is optained
// or is created in the main thread
mainThreadDispatcher = MainThreadDispatcher.Instance;
EventSystemManager.StartListening("printMessage", AddMessage);
}
// might be executed in a thread
public void AddMessage(string message )
{
// instead of directly executing the code wait for the main thread
// either using a lambda expression
mainThreadDispatcher.Dispatch(() =>
{
GameObject remoteMessage = Instantiate(localMessagePrefab,
chatTransform);
remoteMessage.transform.GetChild(0).GetComponentInChildren<Text>
().text = message;
});
// or if you prefer a method
//mainThreadDispatcher.Dispatch(HandleAddMessage);
}
// should be only executed in the main thread
private void HandleAddMessage()
{
GameObject remoteMessage = Instantiate(localMessagePrefab,
chatTransform);
remoteMessage.transform.GetChild(0).GetComponentInChildren<Text>
().text = message;
}

How to capture Exceptions from threads I have no direct access to?

I have developed a Windows Service capable of running a few plugins. Due to its nature, when developing Windows Services, the Start and Stop methods should run and return as fast as possible. The Start method runs Start methods from all plugins, which also should not block the execution. In this example, both plugins instantiate a Threading.Timer, which run in background.
The execution order happens as follows. The arrows indicate what runs in a different thread:
-> MyService.Start -> pA.Start -> pb.Start -> return
\_> DoWork() \
\_> DoWork()
Since both DoWork() are running inside a Timer, if an Exception happens, I am unable to catch it. This could easily be avoided if I could modify PluginA and PluginB, but I can't.
Any suggestion on what I could do to avoid this issue? Thanks in advance.
The following code is an oversimplification of the real code:
public class MyService
{
private PluginA pA = new PluginA();
private PluginB pB = new PluginB();
// Windows Service runs Start when the service starts. It must return ASAP
public void Start()
{
// try..catch doesn't capture PluginB's exception
pA.Start();
pB.Start();
}
// Windows Service runs Stop when the service Stops. It must return ASAP
public void Stop()
{
pA.Stop();
pB.Stop();
}
}
// I have no control over how this is developed
public class PluginA
{
private Timer _timer;
public void Start()
{
_timer = new Timer(
(e) => DoWork(),
null,
TimeSpan.Zero,
TimeSpan.FromSeconds(10));
}
private void DoWork()
{
File.AppendAllText(
"C:/log.txt",
"hello" + Environment.NewLine);
}
public void Stop()
{
_timer.Change(Timeout.Infinite, 0);
}
}
// I have no control over how this is developed
public class PluginB
{
private Timer _timer;
public void Start()
{
_timer = new Timer(
(e) => DoWork(),
null,
TimeSpan.Zero,
TimeSpan.FromSeconds(10));
}
private void DoWork()
{
File.AppendAllText(
"C:/log.txt",
"Goodbye" + Environment.NewLine);
throw new Exception("Goodbye");
}
public void Stop()
{
_timer.Change(Timeout.Infinite, 0);
}
}
You can also use the AppDomain.UnhandledException Event.
Please note that you can't recover from such an exception.

C# Multithreading with a Singleton object involved

Somewhere on my main thread i make a new thread which creates an object that is only allowed to be instantiated once through the entire application time.
Further down my main thread i have a function that makes use of this object that is also a global variable by the way.
So i wish to run this function on the same thread that the object was created.
Question is how can i achieve this when it is the Main threads decision when this function should be called?
// global variable
private static IWebDriver driver;
// Main Thread thread creation
Thread thread = new Thread(() =>
{
driver = new ChromeDriver(#"myPath");
});
thread.Start();
// some click event on main thread
myFunctionUsingDriverObject();
So i need some way to tell the function to run on the same thread as driver was created. Usually you would use the methodInvoker but the IWebDriver does not have such a method. So is there another way i can invoke the function into the thread of driver?
If anyone is wondering why i want to do this. Then it is because the UI is run on the Main Thread and then the function will freeze the UI until completion if it is also run on the main thread.
Add a reference to the WindowsBase.dll and write this code:
private static IWebDriver driver;
private static Dispatcher dispatcher = null;
AutoResetEvent waitHandle = new AutoResetEvent(false);
var thread = new Thread(() =>
{
dispatcher = Dispatcher.CurrentDispatcher;
waitHandle.Set();
Dispatcher.Run();
});
thread.Start();
waitHandle.WaitOne();
// Now you can use dispatcher.Invoke anywhere you want
dispatcher.Invoke(() =>
{
driver = new ChromeDriver(#"myPath");
});
// And async for not blocking the UI thread
dispatcher.BeginInvoke(new Action(() =>
{
myFunctionUsingDriverObject();
}));
// or using await
await dispatcher.InvokeAsync(() =>
{
});
// And when you are done, you can shut the thread down
dispatcher.InvokeShutdown();
You could use a singleton class or if you wanted to ensure that this could only run once for all applications, a service class that is based on a Mutex. I will show you the former as this seems more applicable as far as I can make out
public interface IDriverService
{
void StartDriverService();
void StopDriverService();
void PerformDriverAction();
}
Now an implementation
public class ChromeDriverService : IDriverService
{
private static ChromeDriverService instance;
private readonly Thread _thread;
private readonly ConcurrentQueue<Action> _actions = new ConcurrentQueue<Action>();
private volatile bool _running;
private ChromeDriverService()
{
_thread = new Thread();
_thread.Start();
}
public static IDriverService Instance()
{
if (instance == null)
instance = new ChromeDriverService();
return instance;
}
// This will run on the "target" _thread
public void StartDriverService()
{
while (true)
{
Action action;
if (_actions.TryDequeue(out action))
{
try
{
action();
}
catch (Exception ex) { // Handle }
}
else
{
if (!_running && _actions.IsEmpty)
return;
}
}
}
public void StopDriverService()
{
_running = false;
// IMPORTANT: Finish the logic here - we have a chance for race conditions. Dequeuing before the
// last action runs for example. This is primative, but you will have to take care of this.
while (!_actions.IsEmpty)
{
// Do stuff.
}
}
// Called from some other thread.
public void PerformDriverAction(Action action)
{
if (_running)
_actions.Enqueue(action);
}
}
This is a primitive example and I have not attempted to run or compile this (I am on my phone).
Note, I am not doing anything with the actual ChromeDriver. This class can be simply edited to work with such an object.
I hope this helps.

Categories

Resources