- Home /
How to prevent heavy method calculation from locking Unity up?
Hello, everyone!
Right now Im having an issue with a method, called from a DLL, in which it can take a long time before this method finishes its calculation and in the mean time I simply cannot use anything on Unity.
I tried using a thread (see below) so that Unity could continue its tasks while my method calculation was being done, but that didnt work out.
IntPtr resultPointer = default(IntPtr);
Debug.Log( "Startin thread... " );
System.Threading.Thread thread = new System.Threading.Thread(
() =>
{
resultPointer = computeMinBox3D( _pointsFloatVector.Length, _pointsFloatVector, 8 );
}
);
thread.Start( );
thread.Join( );
Debug.Log( "Thread work done!" );
I read some of the coroutine documentation and figured it wouldnt solve my problem either.
My second try is the following:
private IEnumerator YieldComputeMinBox3D( int pointsFloatVectorLength, float[] pointsFloatVector, int numThread )
{
_resultPointer = computeMinBox3D( pointsFloatVectorLength, pointsFloatVector, numThread );
yield return null;
}
Altough this one does prevent Unity from locking up it also doesnt execute the method correctly, it seems to just skip its heavy calculation completely and doesnt continue where it is supposed to. What am I missing here?
Extra question: The method Im calling is already multithreaded. Would this be a problem?
Answer by Dave-Carlile · Jun 17, 2015 at 06:06 PM
In your thread version you're calling thread.Join
. This waits for the thread complete, which defeats the purpose.
You need to start the thread and let it run in parallel. At some point, possibly multiple frames in the future, that thread will complete its work and you can grab the result. You'll need a way to communicate back to the main thread that the thread is complete. Then you get into synchronization issues and such which are beyond the scope here.
.NET has Tasks that would make this a bit easier, but I don't think the version of Mono that Unity uses has those? Not 100% sure about that though.
Your only other option is to manually split the work across multiple frames, with coroutines or something, but that doesn't seem very feasible unless the dll is yours and you can modify it.
I was going for async threads with Tasks right now but System.Threading doesnt have the Task class.
Ill try something with the sync issues before modifying my dll (I do have its souce code).
If I really need to touch the dll, would you have any example on how to modify it to split work accross multiple frames?
Thanks!
Split compute$$anonymous$$inBox3D
into multiple pieces that each run quickly. Use a coroutine in Unity that calls the first piece, yields to the next frame, calls the second piece, etc. If you set up the new compute$$anonymous$$inBox3D
right you can just call the same function and pass in a state enum or something. Or you might need to pass a class back and forth if there's more state that needs to be preserved between executions.
But if you're going to go through all that trouble, you might as well code it in C# (I assume it isn't already because of your use of IntPtr) in Unity and turn it into a proper coroutine.
The accepted answer to this question gives a good overview with example code for how to thread in Unity. It includes how to poll the threaded job for completion. (See near the bottom of the answer.) If you are doing this in the Unity Editor rather than in play mode, then the EditorApplication.update callback can be used ins$$anonymous$$d of $$anonymous$$onoBehaviour.Update().
The thing to remember about coroutines is that they are really just a way to flexibly schedule progressive tasks. ($$anonymous$$g. Do this, wait a bit, then do that, wait again, then do this final thing.) Everything they do still happens on the main application thread. So if there is heavy work in a section of code in a co-routine it will still block the main application.
Although I had already seen that question several times, it took me some time to decide to try it, but once I did, it did solve my problem :)
In the end I had to split my heavy calculation method in 3 steps:
->PreProcess ->DLL call in a new thread (where unity was freezing) ->PostProcess
Since this method is triggered by a UI button, I decided to make the following:
private void $$anonymous$$anageThreadedJob( ref bool apply, ThreadedJob job, Action PreProcess, Action PostProcess )
{
if( apply && job == null )
{
PreProcess( );
apply = false;
}
if( job != null )
{
if( job.Update( ) && _threadWorkIsDone == false )
{
PostProcess( );
PostProcessGenerate( );
_threadWorkIsDone = true;
}
}
}
This way I can pass multiple dll calls to threads in a generic way.
Thanks everyone for the help!