UniTask's documentation recommends to avoid "async void" and use UniTaskVoid instead. But I cannot find solution to subscribe to events
async void subscription
public class WindowGamePause : WindowView
{
[SerializeField] private Button _restartGameButton;
private void Awake()
{
_restartGameButton.onClick.AddListener(RestartGame);
}
private async void RestartGame()
{
await Close();
_gameManager.RestartGame();
}
}
async UniTaskVoid subscription??
public class WindowGamePause : WindowView
{
[SerializeField] private Button _restartGameButton;
private void Awake()
{
_restartGameButton.onClick.AddListener(RestartGame); //What to do?
_restartGameButton.onClick.AddListener(RestartGame().Forget); //Doesn't work correctly - the method fires on the line
}
private async UniTaskVoid RestartGame()
{
await Close();
_gameManager.RestartGame();
}
}
[UPDATE]
I found some solution. It works fine but I do something wrong because Rider says: "Closure can be eliminated: method has overload to avoid closure creation". And I don't have any ideas to avoid closures.
public class WindowGamePause : WindowView
{
[SerializeField] private Button _restartGameButton;
private void Awake()
{
_restartGameButton.onClick.AddListener(RestartGame);
}
private void RestartGame()
{
UniTask.Void( async () =>
{
await Close();
_gameManager.RestartGame();
});
}
}
You can always simply add an in-between void like
private void Awake()
{
_restartGameButton.onClick.AddListener(RestartGame);
}
private void RestartGame()
{
RestartGame.Forget();
}
private async UniTaskVoid RestartGameAsync()
{
await Close();
_gameManager.RestartGame();
}
This basically could also be done with a lambda (but then you can't remove the listener in case it is ever needed)
_restartGameButton.onClick.AddListener(() => RestartGameAsync().Forget());
The problem is that in order to call Forget you already have to call the method and return a UniTaskVoid which will be immediately running.
Related
I am having a problem while trying to call StartCoroutine from the body of an event handler. I am expecting to print the following on the console, instead it only prints "before" and it does not start the coroutine:
before
inside
working
after
private void Awake()
{
socket.OnMessage += (sender,e) => {
Debug.Log("before");
StartCoroutine(handleIncommingMessages(sender,e));
Debug.Log("after");
};
}
public IEnumerator handleIncommingMessages(object sender, MessageEventArgs e) {
Debug.Log("inside");
yield return new WaitForSeconds(3f);
Debug.Log("working");
}
This coroutine only works in main thread. You have to inject the method of the coroutine you want to execute to the main thread.
Usually when you try to run it in another thread you get this error:
UnityException: IsObjectMonoBehaviour can only be called from the main thread.
I solved this by defining a public List and using Update method.
List<Action> threadPumpList = new List<Action>();
private void Update()
{
while (threadPumpList.Count > 0)
{
threadPumpList[0].Invoke();
threadPumpList.RemoveAt(0);
}
}
public void WriteTestMessage()
{
Debug.Log("Hello World.")
}
public void Test()
{
threadPumpList.Add(() => WriteTestMessage());
}
You can use this like this.
List<IEnumerator> threadPumpList = new List<IEnumerator>();
private void Update()
{
while (threadPumpList.Count > 0)
{
StartCoroutine(threadPumpList[0]);
threadPumpList.RemoveAt(0);
}
}
private void Awake()
{
//socket.OnMessage += (sender,e) =>
new Thread(() => //You should use socket right here.
{
Debug.Log("before");
threadPumpList.Add(handleIncommingMessages(null, ""));
Debug.Log("after");
}).Start();
}
public IEnumerator handleIncommingMessages(object sender, string e) //I changed parameters for testing.
{
Debug.Log("inside");
yield return new WaitForSeconds(3f);
Debug.Log("working");
}
When I test it the output is like this:
-before
-after
-inside
(3 seconds wait)
-working
There might be issues with the Coroutine being discarded once the event call finishes. I do not know how to fix it - but if you are using a newer version of Unity you can use tasks instead:
private void Awake()
{
// We add async so that we can use the `await` call.
socket.OnMessage += async (sender,e) => {
Debug.Log("before");
// Force it to await when the incoming message as not to close the thread
await handleIncommingMessages(sender, e);
Debug.Log("after");
};
}
public async Task handleIncommingMessages(object sender, MessageEventArgs e) {
Debug.Log("inside");
await Task.Delay(1000);
Debug.Log("working");
}
If that doesn't work try:
public async void handleIncommingMessages instead of Task
I'm currently working on a .net 5 Blazor application.
I use events to pass data from one component to another.
Unfortunately my current logic is synchronous - but I would rather use an asynchronous event handler.
Thus, I need to use the following code to handle my event:
Task.Run(async () => await ChangeNumbers());
Is there a possibility to handle events asynchronously without Task.Run?
My State service looks like this:
public class MyComponentState
{
public int MyNumber { get; set; }
// Is there a way to declare this event Action async??
public event Action OnChange;
public void DoStuff(int myNumber)
{
MyNumber = myNumber;
NotifyStateChanged();
}
private void NotifyStateChanged() => OnChange?.Invoke();
}
The component to handle the state looks like this:
public class MyOtherComponentDisplay : ComponentBase, IDisposable
{
[Inject]
public MyComponentState MyComponentState { get; set; }
protected override void OnInitialized()
{
// this should all be handled async... i could use OnInitializedAsync
MyComponentState.OnChange += OnChangeHandler;
}
private void OnChangeHandler()
{
// Is there a way to handle/declare this without Task.Run(async ...) - but async right away??
Task.Run(async () => await ChangeNumbers());
}
private async Task ChangeNumbers()
{
// Some operations with MyComponentState.MyNumber async needed!!!
StateHasChanged();
}
public void Dispose()
{
MyComponentState.OnChange -= OnChangeHandler;
}
}
Is there a way to declare and handle events async?
Do you know how to solve this problem?
The basic adoptation would be an async void handler:
private async void OnChangeHandler()
{
// Is there a way to handle/declare this without Task.Run(async ...)
// - but async right away??
// Task.Run(async () => await ChangeNumbers());
await ChangeNumbers();
await InvokeAsync(StateHasChanged); // probably needed
}
The way you're doing things looks strange to me. That's not how I do events in Blazor. (Maybe you're coming from Web Forms?)
Generally, a custom event is defined like:
MyControl.razor
[Parameter]
public EventCallback<SomeType> EventName{ get; set; }
#code {
someMethod (){
EventName.InvokeAsync(SomeType data);
}
}
And the handler in the consuming control can be async if you want:
MyPage.razor
<MyControl EventName=OnChangeHandler />
#code {
private async Task OnChangeHandler()
{
await ChangeNumbers();
}
}
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;
}
In my project I use a manager to control a plugin. The main idea is that this plugin must work only in single thread in multythreads WPF application. There is only one instance of plugin in PluginController.
So when I call Start method: it stops plugin (if running) and start it with new argument. Few times a second plugin notificate caller about it's state, and ViewModel shows it in the WPF window.
When I call method Start some times one after one, i see that the previous instance of ViewModel is not destroyed, but only sleeps after Stop. And it calls Update method as god as a new one instance. So my interface twiches becouse two instances are updating it's state. In log is see alternately lines from first one and second one.
But when I call Start(...) then Stop() and then Start(...) again everything works fine.
So
SomeManager.Start(...);
SomeManager.Start(...);
works with errors. And
SomeManager.Start(...);
SomeManager.Stop();
SomeManager.Start(...);
works fine. Can anybody explain me my mistake?
Down lied simplified code.
public static SomeManager
{
public static void Start(SomeArg arg)
{
Stop(); // forgotten code
var vm = GetMainPageVM();
vm.SomeVM = new SomeViewModel(arg);
vm.SomeVM.StartCommand.Execute(null);
}
public static void Stop()
{
var vm = GetMainPageVM();
if (vm.SomeVM != null)
{
vm.SomeVM.Stop();
vm.SomeVM.Dispose();
vm.SomeVM = null;
}
}
}
public sealed SomeViewModel : ViewModelBase, IDisposable
{
private readonly Guid _guid = Guid.NewGuid();
private IPlugin _plugin;
private SomeArg _arg;
public ICommand StartCommand {get; }
public CancellationTokenSource Source {get; }
public SomeViewModel(SomeArg arg)
{
this._arg = arg;
this._plugin = PluginController.GetPluginByName("SomePlugin");
StartCommand = new RelayCommand(StartAsync);
}
~SomeViewModel()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{ ... }
private async Task StartAsync()
{
var progress = new Progress<ISomeProgress>(Update);
try
{
await StartImplementationAsync(progress).ConfigureAwait(false);
}
catch (Exception e) { ... }
}
private async Task StartImplementationAsync(Progress<ISomeProgress> progress)
{
var result = await this._plugin.startAsync(
this._arg,
progress,
this.Source.Token
).ConfigureAwait(false);
}
public void Stop()
{
this._plugin.Stop();
}
private void Update() {log.Debug($"this._guid" ....); }
}
public sealed SomePlugin: IPlugin
{
public async Task<SomeResult> StartAsync(SomeArg args, IProgress<SomeProgress>, CancellationToken cancellationToken)
{ ... }
public void Stop() { ... }
}
UPDATE: I think the problem in simple words is : how to correctly cancel async operation in IDisposable object in normal case with CancellationTokenSource.Cancel() and in unnormal case when Dispose() or Finalizer is called
I'm a Chinese developers, English is not very good, please predecessors read it for editing, thanks in advance.
In Unity, i write one script to listener an Event and in delegate function, i add another script the same listener in previous Event, but why the same listener be called immediately?
The following is add another script the same listener script:
using System.Collections.Generic;
using UnityEngine;
public class SceneManager : MonoBehaviour
{
private static SceneManager m_Instance;
public static SceneManager Instance
{
get
{
if (m_Instance == null)
{
m_Instance = (SceneManager)FindObjectOfType(typeof(SceneManager));
}
if (!m_Instance)
{
Debug.LogError("SceneManager could not find himself!");
}
return m_Instance;
}
}
[HideInInspector]
public List<EventDelegate> EventDelegetDo = new List<EventDelegate>();
public void RegisterBackBtnDo()
{
EventDelegate.Add(EventDelegetDo, Do);
}
public void UnRegisterBackBtnDo()
{
EventDelegate.Remove(EventDelegetDo, Do);
}
private void Start()
{
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.T))
{
EventDelegate.Execute(EventDelegetDo);
}
}
private void Do()
{
Debug.Log("===============ZJDebug=============== From SceneManager");
}
}
The following is initial register listener script:
using UnityEngine;
public class TDelegate : MonoBehaviour
{
public void RegisterBackBtnDo()
{
EventDelegate.Add(SceneManager.Instance.EventDelegetDo, Do);
}
public void UnRegisterBackBtnDo()
{
EventDelegate.Remove(SceneManager.Instance.EventDelegetDo, Do);
}
private void Start()
{
RegisterBackBtnDo();
}
private void Update()
{
}
private void Do()
{
Debug.Log("===============ZJDebug=============== From TDelegate");
SceneManager.Instance.RegisterBackBtnDo();
//Invoke("WaitForTest", float.NegativeInfinity);
}
private void WaitForTest()
{
SceneManager.Instance.RegisterBackBtnDo();
}
}
I have tried multiple variations of this, if invoke float.NegativeInfinity second register it not be called Immediately, can someone tell me why? in addition to delay call There is no better way called by after? English is too bad please forgive me thanks!