-
Notifications
You must be signed in to change notification settings - Fork 891
Application.isPlaying causing exception #33
Comments
The "How To Use for Multithreading" example in README.md is also affected by this issue. |
We've found a workaround for Application.isPlaying. We need to test it a bit and clean it up, but the gist of it is this:
|
See neuecc/issues/33. Application.isPlaying cannot be accessed from a worker thread. Workaround with a new ScenePlaybackDetector class. The PostProcessScene callback is called when a scene starts. The playmodeStateChanged event detects when a scene stops and is subscribed to when the Unity Editor is launched.
Thanks, I'll check quickly. |
When is EditorThreadDispatcher.Dispose() supposed to be called? I added |
Sorry for delay response.
EditorThreadDispatcher.Dispose is never called. |
Edit: Oops, I just saw you already added the work-around to UniRx 5 hours ago :) We have found another related issue: when subscribing for the first time from a worker thread, MainThreadDispatcher.Initialize(), which wants to create a GameObject, is called from the worker thread also. A quick but dirty work-around is: ScenePlaybackDetector.cs
Downside: A MainDispatcherThread GameObject is added to every scene that is started, instead of only when UniRx is used for the first time.
Maybe so. I can't think of a situation where one wants to dispose the EditorThreadDispatcher. |
When Awake void Awake()
{
MainThreadDispatcher.OnApplicationQuitAsObservable().Subscribe();
} I changed in 1bcb411
maybe works fine. |
I want to call |
You are right, the order is like this: Press Play button EditorApplication.playmodeStateChanged (isPlaying = false; isPlayingOrWillChangePlaymode= true) I will test more callbacks. If I can't find a callback for the exact moment between pressing the Play Scene button and before any scripts on game objects are handled, I will ask the Unity Team for help. I think EditorApplication.playmodeStateChanged should be called before Awake, but it's called after Start! |
Alright, instead of
it seems we can use
DidReloadScripts is not only triggered on scene start, but also for example when editing and creating new files. Therefore it's possible that multiple MainThreadDispatchers are added to the same scene. So we need to polish this approach a bit more. |
I thought I had a solution with the code below. Unfortunately when OnDidReloadScripts is called, all variables are reset, including static variables in static classes.
Looking for a new solution. :( |
Thank you for an investigation. If initialized = true, MainThreadDispatcher exists. If initialize on application entry point, this solution is fine.
Slightly serious. |
If isPlaying = true is default private static bool _isPlaying = true; failed is only inEditor and Awake, it's still better. |
I will test it with all use-cases:
In my opinion it should be possible to solve this issue for all use-cases, without requiring the user to manually initialize MainThreadDispatcher. By the way, Mike Talbot aka WhyDoIDoIt has created a similar dispatcher for Unity called Loom. And users have reported similar problems with get_isPlaying as in this issue. https://www.google.com/search?q=loom+get_isplaying |
After exploring many options, I think the best way is this: Summary In the Editor: MainThreadDispatcher will always run on EditorApplication.update. I will create a commit with those changes soon after cleaning up. In the Editor I think we can change MainThreadDispatcher to always run on EditorApplication.update in the Unity Editor. No need to switch to MonoBehaviour.Update() when the scene starts. No need to check Application.isPlaying. That simplifies the logic of MainThreadDispatcher and MainThreadScheduler. We can also remove ScenePlaybackDetector. (I have an ugly backup plan if MonoBehaviour.Update() turns out to be absolutely necessary. We can save _aboutToStartScene in EditorPrefs and read it back in OnDidReloadScripts.) In the Build Outside the Unity Editor there is no notification that the game has started. There is no such thing as [InitializeOnLoad] for builds. The dispatcher has to be on the main thread before Awake(). The only way to guarantee that is when the MainThreadDispatcher is in the scene before building the game. We could make a script that automatically adds a MainThreadDispatcher to the scene. But we don't know whether the scene uses UniRx. MainThreadDispatcher would have to be added to every scene. I do not favor that approach, because it adds a dependency to UniRx. Therefore I suggest MainThreadDispatcher must be added to the scene explicitly. Either:
If UniRx is called and there is no MainThreadDispatcher, show console error "UniRx requires a MainThreadDispatcher in the scene". Execution order To make sure MainThreadDispatcher.Awake() is run before other scripts' Awake(), we need to change its Script Order Execution. There are several ways:
|
Thanks. I think always use EditorApplication.update in the editor is not good idea. void Awake()
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
.Subscribe(x => Debug.Log(Input.mousePosition));
} Sometimes output point is diffrent(maybe take from editor point?). Threfore, In the Edit(isPlaying) and In the Build are diferrent environment is not desirable. // In the build, currently MainThreadDispatcher is loaded automatically(when first call MainThreadDispatcher.Instance). |
I think the latest commit 8707f01 addresses your concerns. ScenePlayDetector is now using EditorPrefs. ScenePlayDetector.IsPlaying will now be true before Awake(). But I just found a bug related to an earlier commit:
When you stop the scene in the editor, the static variable MainThreadDispatcher.initialized is still true. This will cause editor scripts to look for a MainThreadDispatcher that's already removed. To fix it we could do something like this:
or, since we can now determine when the scene has started, maybe we don't need if(!initialized) anymore. |
Awesome! Oh, It's a bug < initialized is true in the editor(editor). |
Thanks, I'm glad that we are close to a resolution to this issue! I added some fixes. Yesterday I accidentally added the wrong .meta file for example, so the WorkerThreadTest scene was probably broken. Also, I created a new example scene called MultipleDispatchersTest. It demonstrates a rare case where there are multiple dispatchers in the scene. I was wondering if a running timer-based Observable such as an Interval could be moved from a destroyed MainThreadDispatcher to another one. But I have a feeling I'm moving way too far into theoretical situations rather than practical ones. However, there is one reasonable situation: scene switching. What if you add MainThreadDispatcher to a scene and LoadLevel another scene during run-time? Awake() does not include DontDestroyOnLoad(instance), so the dispatcher will be destroyed. In the loaded scene there will be a new dispatcher or none at all. Since co-routines are bound to the game object, any previously running Observables depending on co-routines will be stopped when the scene switches. Maybe that's a good thing. I'm not sure what the expected behavior is for this situation. The author of Loom had this to say about this topic:
I should probably move this to a new issue :P
Which one do you mean? |
I checked your MultipleDispatchersTest, CullingMode and any other test scenes.
Ah, I thought Destroy dispatcher for only Editor is unnatural behaviour. |
I have one question. |
Good question. In the Unity Editor Thread.CurrentThread.ManagedThreadId is often 1, even worker threads. But in a Windows build the threads' ManagedThreadIds are different. I'm not sure what causes that difference. I haven't tested other platforms. But I think the main thread ManagedThreadId is guaranteed to be 1. You are asking because of this, right?
It was based on this: http://stackoverflow.com/questions/2374451/how-to-tell-if-a-thread-is-the-main-thread-in-c-sharp but the accepted answer's code didn't work in Unity. Maybe checking for ApartmentState alone is enough. If it's a problem we can remove CheckForMainThread() and replace it with a try/catch block. The only downside is that it's not certain the exception comes from accessing Unity API from worker thread, but it's highly likely. Anyway, it's only for displaying a nice message. |
Yes, I mentioned about CheckForMainThread. (Sorry, it's 1 rather than 0) Unity's ManagedThreadId is different from .NET CLR and let alone do not know the behavior of the other platforms.
But ofcourse it can use after initialize. |
Done. Note that when a game is built and run with the "Development Build" setting disabled, exceptions are skipped and the application will attempt to continue. For example, the WorkerThreadTest that was designed to throw an exception will run as if the MainThreadDispatcher was added. That surprised me. I'm curious how it's possible that the dispatcher is created from a worker thread. |
Sorry for late reply. I thank for your hard work .
Only way have possibilities, pass MonoBhaviour from the outside? |
You're welcome :) (Congratulations on your upcoming release!)
I found a possibly related question: Uncaught exception doesn't kill the application I think Unity will throw errors when attempting to do thread-unsafe things in the editor and in the development build. Such as adding MonoBehaviour from worker threads. That way the developer can fix it. However, in the release build you don't want to annoy the player with errors. Therefore Unity will continue to do unsafe things hoping for the best, although it might eventually lead to unexpected behavior or even a crash. |
MainThreadDispatcher fixes for issue neuecc#33
See neuecc/UniRx/issues/33. Application.isPlaying cannot be accessed from a worker thread. Workaround with a new ScenePlaybackDetector class. The PostProcessScene callback is called when a scene starts. The playmodeStateChanged event detects when a scene stops and is subscribed to when the Unity Editor is launched.
Hello!
This is my code:
Issue is coming from:
MainThreadDispatcher.cs : 164
When I schedule actions from another thread to the Main Thread this line
causes exception
get_isPlaying cannot can only be called on the Main Thread
.Because Applicationn.isPlay is invoked from another thread.
Ofcourse, in the build it works as expected without exceptions
I wonder if it will be a good fix, to just remove this check
if (!Application.isPlaying)
And let everything work inside EditorThread while in UNITY_EDITOR?
The text was updated successfully, but these errors were encountered: