Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

[50USD] Easy threading - Multi threading made easy!

Discussion in 'Assets and Asset Store' started by arklay_corp, Aug 18, 2014.

  1. TheCode

    TheCode

    Joined:
    Dec 24, 2013
    Posts:
    7
    It not helps, just as was before, result is messy: 111213141 and so on
    I need to somehow push job to main thread as quick as possible, wait for result and only then proceed to next loop
     
  2. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    Code (CSharp):
    1. Debug.Log("1");
    2. Task<Vector3>.RunInMainThread(() =>
    3. {
    4.     Debug.Log("2");
    5.     return Ray(from, to);
    6. }).ContinueWith((t) =>
    7. {
    8.     Debug.Log("3");
    9.     Vector3 rayResult = t.Result;
    10.     DoSomeStuffWithTheResultFromTheRay(rayResult);
    11.     Debug.Log("4");
    12.    NextLoop();
    13. });
    Instead of launching all iterations at the same time you should launch the next loop after receiving the result. You cannot invoke the main thread "as quick as possible", it will execute in its update cycle (you could use the draw cycle if you want, but i wouldn't recommend it).

    This is the only way of obtaining output:
    123412341234

    And yes, it's very slow because you are waiting for the main thread in every iteration. If you could do your calculations in parallel and then come back to the main thread when all calculations are ready. Getting this output:

    1111111111111234


    You could use Task.WhenAll()
     
  3. Jurassic

    Jurassic

    Joined:
    Jul 12, 2013
    Posts:
    8
    I'm having some issues with a thread sometimes never completing. I'm creating a group of threads and waiting for them all to finish, and then calling the function to create the tasks again to create a loop to simply continue processing a new group of tasks as fast as it can. It works the majority of the time, but it occasionally one thread will never complete, stopping the whole thing because the WhenAll never got the last thread in the group. I added this to an Update loop to try to catch it, but it doesn't seem to be catching it:

    Code (csharp):
    1.  
    2.     void Update()
    3.     {
    4.         foreach(List<Task> task in _tasks)
    5.         {
    6.             foreach(Task t in task)
    7.             {
    8.                 if (t.IsFaulted)
    9.                     Debug.LogError(t.Exception);
    10.             }
    11.         }
    12.     }
    13.  
    This is the task that gets dropped:

    Code (csharp):
    1.  
    2.                 Task processNationRoutine = Task<List<Citizen>>.Run(() => {
    3.                     List<Citizen> output = new List<Citizen>();
    4.                     try
    5.                     {
    6.                         output = n.DailyRoutine(citzToProcess);
    7.                     }
    8.                     catch (System.Exception e)
    9.                     {
    10.                         Task.RunInMainThread(()=>Debug.LogError(e));
    11.                     }
    12.                     return output;
    13.                     }).ContinueInMainThreadWith((t)=>{
    14.                         if (t.IsFaulted || t.Exception != null)
    15.                             Debug.Log(t.Exception);
    16.                         else
    17.                         {
    18.                             AddCitzToFinishedDailyRoutineList(t.Result);
    19.                             nationRoutineProcessingTask.Remove(t);
    20.                             //Debug.Log("task completed");
    21.                         }
    22.                     });
    23.  
     
    Last edited: Aug 7, 2016
  4. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    Try something like this:
    Code (CSharp):
    1. System.Random rnd = new System.Random();
    2.     class Citizen
    3.     {
    4.     }
    5.     List<Task> tasks = null;
    6.     void CreateTasks()
    7.     {
    8.         finishedTasks = new List<int>();
    9.         tasksFinished = 0;
    10.         int nbOfLists = rnd.Next(10, 20);
    11.         tasks = new List<Task>();
    12.         for (int i = 0; i < nbOfLists; i++)
    13.         {
    14.             tasks.Add(CreateTask(i));
    15.         }
    16.         Task.WhenAll(tasks).ContinueWith(t =>
    17.         {
    18.             tasks = null;
    19.             if (t.IsFaulted)
    20.             {
    21.                 Debug.LogWarningFormat("Error processing: {0}", t.Exception);
    22.             }
    23.             else
    24.             {
    25.                 Debug.LogWarningFormat("All finished");
    26.             }
    27.         });
    28.     }
    29.  
    30.     int tasksFinished = 0;
    31.     Task CreateTask(int index)
    32.     {
    33.         return Task<List<Citizen>>.Run(() =>
    34.         {
    35.             List<Citizen> output = new List<Citizen>();
    36.             try
    37.             {
    38.                 int nbOfCitizens = rnd.Next(100, 1000);
    39.                 for (int i = 0; i < nbOfCitizens; i++)
    40.                 {
    41.                     // mock some process time
    42.                     System.Threading.Thread.Sleep(rnd.Next(1, 10));
    43.                     if(rnd.Next(1,10000) < 5)
    44.                     {
    45.                         throw new Exception("Random error");
    46.                     }
    47.                     output.Add(new Citizen());
    48.                 }
    49.                 Debug.LogFormat("List {0} finished", index); // no need to bring debug.log to main thread
    50.             }
    51.             catch (System.Exception e)
    52.             {
    53.                 Debug.LogFormat("Error in list {0}", index);
    54.                 throw e; // throw again to be catched in the continue with
    55.             }
    56.             return output;
    57.         }).ContinueInMainThreadWith((t) => {
    58.             Debug.LogFormat("{0}/{1} Finished", ++tasksFinished, tasks.Count);
    59.             if (t.IsFaulted)
    60.             {
    61.                 Debug.LogFormat("Error in list {0} [Continuation action]: {1}", index, t.Exception);
    62.                 throw t.Exception; // throw again (this a different task from the Task<List<Citizens>>
    63.             }
    64.             else
    65.             {
    66.                 Debug.LogFormat("List {0} finished [Continuation action]", index);
    67.             }
    68.         });
    69.     }
    70.     List<int> finishedTasks = new List<int>();
    71.     public void Update()
    72.     {
    73.         if (tasks != null)
    74.         {
    75.             int index = 0;
    76.             foreach (Task t in tasks)
    77.             {
    78.                 if (!finishedTasks.Contains(index))
    79.                 {
    80.                     if (t.IsCompleted)
    81.                     {
    82.                         finishedTasks.Add(index);
    83.                         if (t.IsFaulted)
    84.                         {
    85.                             Debug.LogFormat("Task {0} ended with error ", index, t.Exception);
    86.                         }
    87.                         else
    88.                         {
    89.                             Debug.LogFormat("Task {0} ended with no error ", index);
    90.                         }
    91.                     }
    92.                 }
    93.                 index++;
    94.             }
    95.         }
    96.     }
    It works for me
     
  5. Jurassic

    Jurassic

    Joined:
    Jul 12, 2013
    Posts:
    8
    Hmmmm I've set up something like what you suggest, adding all of my tasks to a finished list and checking that list, but I'm still getting the dropped Tasks and no debug statements catching them.

    Additionally, I have some tasks that are created in Start() of my main MonoBehaviour script, and 9/10 times it works fine but sometimes one or more of these gets dropped with no errors thrown, and sometimes I get this error:

    I have this running in my Update(), and I never get any error output from it:

    Code (csharp):
    1.  
    2.  
    3.         if (_finishedTasks.Count > 0)
    4.         {
    5.             Debug.Log("Clearing " + _finishedTasks.Count + " taskgroups");
    6.  
    7.             for (int i = _finishedTasks.Count - 1; i >= 0; i--)
    8.             {
    9.                 Debug.Log("Clearing taskgroup of size " + _finishedTasks[i].Count);
    10.  
    11.                 for (int k = _finishedTasks[i].Count - 1; k >= 0; k--)
    12.                 {
    13.                     if (_finishedTasks[i][k].IsCompleted && (!_finishedTasks[i][k].IsFaulted || !_finishedTasks[i][k].IsAborted))
    14.                         _finishedTasks[i].RemoveAt(k);
    15.                     else if (_finishedTasks[i][k].Exception != null)
    16.                         Debug.LogError(_finishedTasks[i][k].Exception);
    17.                 }
    18.  
    19.                 if (_finishedTasks[i].Count == 0)
    20.                     Debug.Log("Task group cleared with no faults");
    21.                 else
    22.                     Debug.LogError("Task leftover in taskgroup!");
    23.  
    24.                 _finishedTasks.RemoveAt(i);
    25.             }
    26.         }
    27.  
    This is the initialization function that runs on Start(), which sometimes never completes, with no errors or output from the incomplete Tasks:

    Code (csharp):
    1.  
    2.     public void InitializeCitizens(Nation n, int threadCount = 0)
    3.     {
    4.         if(threadCount == 0)
    5.             threadCount = Task.defaultThreadPool.availableThreads;
    6.  
    7.         Debug.Log("Initializing nation " + n.Name + " with " + threadCount + " threads.");
    8.  
    9.         //  initialize new task group
    10.         List<Task> initializeCitizensTask = new List<Task>();
    11.  
    12.         for (int i = threadCount; i > 0; i--)
    13.         {
    14.             int I = i;
    15.             int segment = n.StartingPopulation / threadCount;
    16.             if (I == 1 && segment * threadCount != n.StartingPopulation)
    17.                 segment = n.StartingPopulation - (segment * (threadCount-1));
    18.  
    19.             //  start task
    20.             Task task = Task<List<Citizen>>.Run(() =>
    21.             {
    22.                 return n.InitializeCitizens(segment);
    23.             }).ContinueInMainThreadWith((t) => {
    24.                 if (t.IsFaulted || t.Exception != null)
    25.                     Debug.LogError(t.Exception);
    26.                 else
    27.                     AddCitzToFinishedProcessList(t.Result);
    28.             });
    29.  
    30.             //  add task to task group
    31.             initializeCitizensTask.Add(task);
    32.         }
    33.  
    34.         //  add task group to master task list
    35.         _tasks.Add(initializeCitizensTask);
    36.  
    37.         Task.WhenAll(initializeCitizensTask).ContinueInMainThreadWith((t) =>
    38.         {
    39.             CitizenProcessingComplete(n);
    40.             //  add finished taskgroup to finishedtasks list
    41.             _finishedTasks.Add(initializeCitizensTask);
    42.             //  remove group from master tasklist
    43.             _tasks.Remove(initializeCitizensTask);
    44.         });
    45.     }
    46.  
    This is the function that is being called in each Task:

    Code (csharp):
    1.  
    2.     public virtual List<Citizen> InitializeCitizens(int number)
    3.     {
    4.         List<Citizen> citizens = new List<Citizen>();
    5.  
    6.         for (int i = 0; i < number; i++)
    7.         {
    8.             citizens.Add(new Citizen(this));
    9.         }
    10.  
    11.         return citizens;
    12.     }
    13.  
     
    Last edited: Aug 8, 2016
  6. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    Try to keep things simple. Unity doesn't catch nor show exceptions in threads different than main, you have to try/catch them (and rethrow them if you need too)

    But if you are catching them in their thread, it DOES to work. So make sure you are surrounding every thread with try/catch/finally sentences and log the exception.

    The exception you are getting seems to be because the final state of a task it's being set twice... try to debug it, it looks weird.
     
  7. Jurassic

    Jurassic

    Joined:
    Jul 12, 2013
    Posts:
    8
    This this is proving quite troublesome. When I get a Task that fails to complete, if I check the status of all of the tasks in my List, there is indeed 1 task that is uncompleted, however it also has no faults and is not aborted. If I try to abort the uncompleted task, I do get an error that the actual thread "m_runThread" inside of the Task is null. I've got what's getting to be a silly amount of try/catches that all never catch anything. There's only two actual Tasks I'm using that should be running outside of the main thread, the two I've described in my previous posts. (RunInMainThread, I assume, does exactly that? Regardless, I've got try/catches in those as well.)
     
  8. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    No, RunInMainThread doesn't create any thread, I executes the action passed in the main thread.

    To create new threads are created using Task.Run and Task<T>.Run, in those actions is where you should add try/catch statements.

    The list you are using (and I included in my example) is just there for debug, you shouldn't use that method to check the state of the task (you may run into synchronization problems), for getting the status of each task you should use the continuation methods.

    Microsoft has a lot of documentation and examples for using their async/await model:
    https://msdn.microsoft.com/en-us/library/mt674882.aspx
     
  9. Jurassic

    Jurassic

    Joined:
    Jul 12, 2013
    Posts:
    8
    Sorry, misworded my reply there, ofc I don't expect that RunInMainThread would create any new threads, that wouldn't make sense. :)

    Is it a bad idea to be grouping my Tasks into a bundled list of Task group list I'm doing? (not the finished tasks)
     
  10. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    I'd rather have an array (or whatever) with the results of the threads (accessing that array inside a lock() {} block) and handling the finalization of all the threads in the continuation action for Task.WhenAll().

    If you check the source code of easy threading you can see the details for the executions, it's quite simple, usually when I run into problems with it (and I'm used to very thread heavy apps) it's because some race condition or some exception not being shown by unity.

    Also if you are developing for android logcat can be helpful.

    If you find yourself in a dead end and you are allowed to, you can send me in private a stripped down version of your project for me to reproduce the error and (hopefully) help you out with it.
     
  11. Jurassic

    Jurassic

    Joined:
    Jul 12, 2013
    Posts:
    8
    Okay so I've been running more tests and the only thing I've found that seems to stop the issue is only using a single thread. (Which of course, defeats the purpose completely) I've stripped down all of my tasks and got rid of the master task list and I'm still getting the same issues. I am creating a task that does as little as possible, adding it to a group of tasks, then doing a WhenAll on that group and printing a debug when all are finished. If I have 4 threads for example, eventually the WhenAll only receives 3 threads and the WhenAll never fires. In fact, this simplified code actually seems to be losing a thread more often than when I was trying to do more. Previously, it could iterate 100 times or more before failing, now it rarely makes it to 20 iterations without losing a thread.

    Code (csharp):
    1.  
    2.     void Test()
    3.     {
    4.         List<Task> test = new List<Task>();
    5.         int threads = Task.defaultThreadPool.availableThreads;
    6.  
    7.         for (int i = 0; i < threads; i++)
    8.         {
    9.             Task task = Task.Run(() =>
    10.             {
    11.                 System.Threading.Thread.Sleep(1000);
    12.             }).ContinueInMainThreadWith((t) =>
    13.             {
    14.                 if (t.IsFaulted)
    15.                     Debug.LogError(t.Exception);
    16.                 else
    17.                     Debug.Log("test");
    18.             });
    19.  
    20.             test.Add(task);
    21.         }
    22.  
    23.         Task.WhenAll(test).ContinueInMainThreadWith((t) =>
    24.         {
    25.             if (t.IsFaulted)
    26.                 Debug.LogError(t);
    27.             else
    28.                 Debug.Log("completed");
    29.  
    30.             Test();
    31.         });
    32.     }
    33.  
    --------------EDIT--------------

    After several days of dealing with this, I think I discovered the problem. I was unnecessarily calling the main thread with ContinueInMainThreadWith on each individual Task in each group, and when 2 tasks completed too close to each other one could get lost or conflict. This is why when I simplified what was being processed down to just a Thread.Sleep the threads got lost much more often, and why I was getting the AlreadyComplete error on my startup threads sometimes. However, I still don't know why I wasn't catching any of the errors the majority of the time (Sometimes in the startup, never in the main loop).

    EDIT AGAIN

    So my little simulation can run for much longer now, and I am actually catching the error that's happening! I set up a List of Tasks again just for debugging, and it didn't get caught in it's own Try-Catch but when I checked that Task for faults. Not sure if it's the same error that was killing my threads before, but once again seems to be related to the state transition on the tasks. Given I'm not directly fiddling with the state of the Tasks, what steps can I take to prevent this? I assume it has something to do with Tasks completing too close to each other?

     
    Last edited: Aug 9, 2016
  12. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    It all sounds like you are having race problems, make sure of using lock() {} where needed.

    That error you are receiving is because you are trying to run a thread that was already finished
     
  13. tolosaoldfan

    tolosaoldfan

    Joined:
    Sep 14, 2012
    Posts:
    92
    Hi all,
    I faced an error when using the AbortThread() method:

    NullReferenceException: Object reference not set to an instance of an object
    U3D.Threading.Tasks.Task.AbortThread () (at Assets/U3D/Threading/Tasks/Task.cs:244)


    The AbortGPSImportTask() function is invoked when I click on a button (named "Abort", yes a really crazy GUI !)

    Code (CSharp):
    1.         m_TaskImportGPSData = Task.Run( () =>
    2.         {
    3.             try
    4.             {
    5.                  HugeData.ConvertData();
    6.             }
    7.             catch(System.Threading.ThreadAbortException tae)
    8.             {
    9.                 Debug.LogError("Task of Importing datas was aborted before finishing");
    10.                 throw tae; // rethrow if you want the task to be set as aborted
    11.              }
    12.            
    13.         } ).ContinueInMainThreadWith(CheckTaskResult);      
    14.    
    15.     }
    16.    
    17.     public void AbortGPSImportTask()
    18.     {
    19.         if (m_TaskImportGPSData != null)
    20.             m_TaskImportGPSData.AbortThread();  //=> Error during runtime
    21.     }
    22.    
    23.     void CheckTaskResult(Task t)
    24.     {
    25.         if (t.IsFaulted)
    26.         {
    27.             Debug.LogError("Error executing task: " + t.Exception);
    28.         }
    29.         else if (t.IsAborted)
    30.         {
    31.             Debug.LogWarning("Error executing task: " + t.Exception);
    32.         }
    33.         else
    34.         {
    35.             Debug.Log("Task executed successfully");
    36.             OpenImportedData();
    37.         }
    38.     }
    Any idea ?
    Thanks,
     
  14. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    Hi tolosaoldfan,

    Check if the task is running before trying to cancel it.
     
  15. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    Hi,

    Can you provide some examples or situations where your asset is usefull?
    I know about threading but I have no idea in what real situatios it's an avantage.

    Thank you.
     
  16. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
  17. zugsoft

    zugsoft

    Joined:
    Apr 23, 2014
    Posts:
    453
    Hello,
    That works with UWP?
     
  18. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    Hi menfou,

    It should work. Give it a try, if it doesn't just ask for the refund and I will grant it
     
  19. zugsoft

    zugsoft

    Joined:
    Apr 23, 2014
    Posts:
    453
    System.Thread doesn't exist in UWP, I don't want to spend money and ask you a refund :D
    Do you use System.Thread?
     
  20. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    System.Threading.Thread is used in the platforms that support it (as far as I know all platforms except WebGL). For webGL we use another approach.

    I've never used UWP, but if it's like WebGL you can modify the code and force to avoid using System.Threadin.Threads.

    But also keep in mind that in this case you won't get background threads, everything will be executed in the main thread (basically because threading in WebGL is not allowed)
     
  21. Threepwood

    Threepwood

    Joined:
    Jul 23, 2009
    Posts:
    95
    Arklay_corp,

    Moved over from the old deprecated LOOM on the Asset Store to this threading plugin.

    Having a couple of issues. I have one thread up and running just fine. Nothing fancy, just kicked one off with Task.Run and test completion with ContinueInMainThreadWith(foo) and set a bool that the thread can kick off again. So, the thread just constantly fires on/off and repeats on a job.

    The problem comes when I want to do 3 more threads. Even just trying to add a second thread, the second thread crawls. I'm implementing it the exact same way as the first thread. Imbedded a couple levels down are some Task.RunInMainThread calls. Nothing too slow.

    But, this second thread crawls. It's actually many times fast just doing the whole thing on the main thread, aside from some hitching.

    Any ideas. I've tried everything I can think of. Bottom line, is it ok to fire off X number of threads via Task.Run, some of which may have calls to Task.RunInMainThread in them.
     
  22. Threepwood

    Threepwood

    Joined:
    Jul 23, 2009
    Posts:
    95
    Arklay,

    Could you also, possibly post a short code snippet to show how you'd setup a thread pool to call a method over and over for as many threads as you wanted to allocate? That would be a really helpful pattern.

    So imagine you have some work that's called via a single method. It's a lot of work so you'd like to throw 1-4 or 5 threads at it.
     
  23. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    Hi Threepwood,

    I'm not sure what you mean the second thread crawls, once a thread is in execution it executes always at the same speed, it doesn't matter how many other threads are sleeping waiting to be executed. Maybe you can share some relevant code? (you can send me a private message if you want) which architecture are you building for?.

    About a method that spawns a thread, again I'm not sure what you mean, you cannot just throw some threads to a method... you can setup a thread to execute a method... If what you want to achieve is to have a method that always execute in a background thread that's easy, lets say you have:


    Code (CSharp):
    1. int VeryExpensiveMethod(string with, int parameters)
    2. {
    3.    // ... does some heavy stuff
    4. }
    You can create and call instead:

    Code (CSharp):
    1. Task<int> VeryExpensiveMethodAsync(string with, int parameters)
    2. {
    3.     return Task<int>.Run(() => VeryExpensiveMethod(with, parameters));
    4. }
    5.  
    6. // invoke it like this:
    7. VeryExpensiveMethodAsync("xxx", 0).ContinueWith((t) =>
    8. {
    9.    // get the result here from t.Result
    10. });
    VeryExpensiveMethodAsync will everytime spawn a thread which will handle the work of VeryExpensiveMethod and will return the result in t.Result
     
  24. Threepwood

    Threepwood

    Joined:
    Jul 23, 2009
    Posts:
    95
    Arklay,

    Building for Mac on a 4 core i7.

    What's the best practice for say, activating a thread in Update, and you want it to be called as soon as it's done, over and over. Here's what I have.

    Code (CSharp):
    1.     void CheckTerrainTask(Task t)
    2.     {
    3.         if (t.IsFaulted)
    4.         {
    5.             Debug.Log("Error executing task: " + t.Exception);
    6.         }
    7.         else
    8.         {
    9.             // tells the thread that it's ok to start a new one since it is over?
    10.             canBuildTerrain = true;
    11.         }
    12.     }
    13.  
    14.     void Update()
    15.     {
    16.         if (canBuildTerrain)
    17.         {
    18.             pos = Camera.main.transform.position;
    19.             // stop any more threads from kicking off
    20.             canBuildTerrain = false;
    21.  
    22.             Task.Run (() =>
    23.             {
    24.                 BuildTerrainColumn(pos);  // slow stuff being done here
    25.             }).ContinueInMainThreadWith(CheckTerrainTask);
    26.         }
    27.     }
    This seems to work fine but I'd like to know if it's ok.

    The issue I was having was that in another class, in another update, I was doing this exact same pattern and that resulting thread ran really REALLY slowly. The results seems to be ok, but it was super slow. The only difference was that at the end of all the method calls, when the thread was about to end and return I had a block of code like this:

    Code (CSharp):
    1.  
    2.     // Last code executed before the thread returns
    3.     Task.Run (() =>
    4.     {
    5.         Task.RunInMainThread( () => FinishMesh(mesh, onlyLight) ).ContinueInMainThreadWith(MeshDone);
    6.               });
    7.     }
    8.  
    9.     public void FinishMesh(MeshFilter mesh, bool onlyLight)
    10.     {
    11.         if (onlyLight)
    12.         {
    13.             ApplyNewColors(mesh);
    14.         }
    15.         else
    16.         {
    17.             mesh.sharedMesh = builder.ToMesh(mesh.sharedMesh, this);
    18.  
    19.             mesh.GetComponent<Renderer>().sharedMaterials = builder.GetMaterials(BlockSet.blockSet.GetMaterials());
    20.         }
    21.     }
    22.  
    So I was at a loss as to what to do next.

    Also would love to see a small example of how you would go about having maybe up to 3 threads that are fired off in a update, calling a method that takes a while. What's the best way to do this?

    Thanks for the support. The plugin is very nice.
     
    Last edited: Nov 29, 2016
  25. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    Hi,

    Your construction looks good, the "canBuildTerrain" flag is very important.

    About the other problem, is not about the continuation action, the problem is that you are still using the main thread for the calculations:

    Code (CSharp):
    1.             Task.Run(() => // <= a background thread is spawned
    2.             {
    3.                 Task.RunInMainThread( // <= execution is redirected BACK to the main thread
    4.                     () => FinishMesh(mesh, onlyLight)).ContinueInMainThreadWith(MeshDone);
    5.             });
    6.  
    You need to have VERY clear where you can use in background threads and what you can't, GetComponent needs to be executed in the main thread so you can do:
    Code (CSharp):
    1.             Task<Material[]>.Run(() =>
    2.             {
    3.                 return builder.GetMaterials(BlockSet.blockSet.GetMaterials());  // this will execute in a background thread
    4.             }).ContinueInMainThreadWith((t) =>
    5.             {
    6.                 Material[] m = t.Result;
    7.                 FinishMesh(mesh, onlyLight); // this will execute in the main thread
    8.                 MeshDone();
    9.             });
    10.  
    Or you can (moreover, you SHOULD) cache the GetComponent call and maybe use the cached renderer on the background thread.

    I'm glad you like the plugin :)
     
  26. gofind

    gofind

    Joined:
    Jul 12, 2016
    Posts:
    4
    Hi Idk if my situation can get help from your package.

    I have a "machine" (say delegate1, delegate2, delegate3,...,delegate6, delegate1 may call delegate2 and pass it a para to continue, 2->3,.., 5->6 and so on), the machine can only do one task one time. Every time I request a result from the machine, I send it a parameter. and it gives me a result from the parameter.

    My issue is that if I send request too frequently (last task hasn't been done), the machine cannot immediately stop the previous task, since Idk where the last task goes (if it is in delegate1 or 2 or...6), if I knew I can stop it by not calling the next delegate. Right now I inserted "if checking" in every delegate, which means if new request comes in, it stops. However, sometimes if the machine stuck somewhere (like a WWW request), the previous task can never stop.

    Solution which comes of my mind is using multithreading to apply something like "thread.stop(task)". But Idk if it can help. I have checked the document you listed, it seems like one task(t1) is a "function(delegate1)". I wonder if I can stop the task by calling t1.stop to stop even the delegate goes to like "delegate4".
     
  27. gofind

    gofind

    Joined:
    Jul 12, 2016
    Posts:
    4
    And what the "dispatcher" works where? It's like Task can do everything
     
  28. gofind

    gofind

    Joined:
    Jul 12, 2016
    Posts:
    4
    I wonder what is the problem of this code,
    Code (CSharp):
    1. Task t;
    2. public void TestExecute(){
    3.     t = Task.RunInMainThread(RunCoro).ContinueInMainThreadWith(CheckTaskRes);
    4. }
    5.  
    6. void CheckTaskRes(Task t){
    7.     if(t.IsAborted){
    8.         WriteLog("t has been aborted");
    9.     }else{
    10.         WriteLog("t hasn't been aborted");
    11.     }
    12. }
    13.  
    14. void RunCoro(){
    15.     StartCoroutine("TestCoro");
    16. }
    17.  
    18. IEnumerator TestCoro(){
    19.     yield return new WaitForSeconds(5.0f);
    20.     WriteLog("coroutine works");
    21. }
    22. void Update(){
    23.     if(Input.GetKeyDown(KeyCode.Space)){
    24.         t.AbortThread();
    25.     }
    26. }
    When I press Space, Editor gives me an error of NullReferenceException, which indicates my task to abort haven't been assigned.
     
  29. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    Hi gofind,

    AbortThread doesn't work on unity, it will never raise ThreadAbortException on your thread, so your approach of having a flag is the correct one, but you are right, if your method is waiting in some internal call like WWW you cannot make it stop inmediately. (EasyThreading implements the Task.Abort methods just in case someday unity decides to update their .net framework implementation and get Thread.Abort to work).

    The dispatcher is used internally but you can also make use of it (just check its public methods), even more, it is required to be initialized before using the whole system.
     
  30. gofind

    gofind

    Joined:
    Jul 12, 2016
    Posts:
    4
    Do you have any suggestion on my issue? Thanks, I have bought your package and run the "leaderboardtest" in the example. But it seems have the same issues as my solution - "if checker". If a WWW stucks, it can never stop.
     
  31. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    There is no workaround, Thread.Abort doesn't work and in this version of .net you cannot pass a cancelationtoken to WWW
     
  32. SureSight

    SureSight

    Joined:
    Aug 2, 2013
    Posts:
    65
    This asset looks really promising. I really wan to get away from coroutines, but the examples are so arbitrary that it's hard to imagine how it might fit with my project.

    Most of the tasks I need are time-based and are interruptable/cancellable.

    What would an example look like if I had a thread that:
    1. Took a parameter of CharacterStatistics object (encapsulates health, mana, etc.)
    2. Increased the CharacterStatistics.mana by 5
    3. Repeats every 3 seconds
    4. Updated the UI mana bar on the main thread with CharacterStatistics.GetNormalisedHealth()
    5. Ignores the mana increase if a spell was cast (interrupt)
    6. Thread is cancelled when character dies (cancel)
     
  33. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    In that case I wouldn't use a Thread or a Coroutine at all, but the Update method. But if for some other reason you need it to be a background thread, it would look like this (I'm supposing this is a monobehaviour which is enabled while the character is alive and disabled when is dead):
    Code (CSharp):
    1.    public void Start()
    2.     {
    3.         Dispatcher.Initialize();
    4.     }
    5.  
    6.     public int initialMana = 50;
    7.     public bool spellWasCast;
    8.     private void OnEnable()
    9.     {
    10.         statisctics.mana = initialMana;
    11.         spellWasCast = false;
    12.         Task.Run(ManageMana);
    13.     }
    14.  
    15.     private void ManageMana()
    16.     {
    17.         while(this.enabled)
    18.         {
    19.             System.Threading.Thread.Sleep(3 * 1000);
    20.             if (!spellWasCast)
    21.             {
    22.                 statisctics.mana += 5;
    23.             }
    24.             spellWasCast = false;
    25.  
    26.             Task.RunInMainThread(statisctics.GetNormalisedHealth);
    27.         }
    28.     }
     
    SureSight likes this.
  34. caglarenes

    caglarenes

    Joined:
    Jul 9, 2015
    Posts:
    45
    Sorry my silly question but I have a question about this asset. I have a image scanning function in a .dll file. So I can't change the function codes.

    I don't scan every webcamtexture frame. I scan every 1 second for performance improving but scan() function drop camera FPS every single time by itself because scanning a frame very heavy. I want create some background thread for scan() function and start another scanning when the other one finish. Scanning rate not important, I only want to protect camera FPS.

    Task.RunInMainThread( myscanfunction ); can solve my problem?
     
  35. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    Hi Caglarenes, sorry for the delay.

    The problem is that you are using "RunInMainThread" which will be executed in the main (foreground) thread, you need to use your scan function in a background thread (Task.Run) and then go back to the main thread (Task.RunInMainThread) when you want to access any unity object
     
  36. caglarenes

    caglarenes

    Joined:
    Jul 9, 2015
    Posts:
    45
    Oh I get the point. I don't have your asset. I'm only try to understand the workflow. Sorry for my bad English, I will ask again in correct way.

    I have a scan() function and I don't have access to this function because it is build in a .dll file. Can I use this function with your asset something like:
    Code (CSharp):
    1. Task.Run(
    2.    scan();
    3. );
     
  37. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    Yes, this would be the correct syntax:
    Code (CSharp):
    1. Task.Run(scan);
     
    caglarenes likes this.
  38. LennartJohansen

    LennartJohansen

    Joined:
    Dec 1, 2014
    Posts:
    2,394
    Is there any garbage collection when creating a thread like this?
     
  39. Arganth

    Arganth

    Joined:
    Jul 31, 2015
    Posts:
    277
    has anyone done some performance measurements?
    whats the best case gain? average case?

    I have done some parallel programming with openMP or MPI,
    just not sure how well threading works with Unity in the background.
    (and monobehaviours, scriptable objects ect.)
     
  40. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    @LennartJohansen there is no garbage collection involved, and the memory footprint for a thread is minimal

    @Arganth it depend a lot in your implementation, the processing u want to do and the hardware... if you have any specific test you want to be run I can do it for you. Anyway the main advantage of doing processing in a background thread is not really perfomance but removing load from the main thread and giving the user a better experience
     
  41. LennartJohansen

    LennartJohansen

    Joined:
    Dec 1, 2014
    Posts:
    2,394
    I have been using ThreadPool.QueueUserWorkItem(for some project to do parallell processing, but I can not seem to get away from some garabage collection every time I create a new thread.
     
  42. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    Is it when you create a new thread or before a background thread starts executing? The framework can use a context switch to launch a GC.Collect... but that's not a problem with the thread but somewhere else (most likely you are destroying too many objects on runtime)
     
  43. aaronjbaptiste

    aaronjbaptiste

    Joined:
    Dec 4, 2012
    Posts:
    62
    Is there a way to listen to the Task.Any (or similar) event with a ThreadPool? I need to run some code when all queued actions are finished.

    Also what's the best way to run code in the main thread with a pool? Currently i'm using the following, however it looks like the Task.RunInMainThread is blocking and it's not threading correctly. Can't figure out to get ContinueInMainThreadWith to work with ThreadPool.

    Code (CSharp):
    1.  
    2. ThreadPool tp = new ThreadPool();
    3. tp.maxThreads = 10;
    4. tp.QueueAction(() =>
    5. {
    6.     Debug.Log("do some stuff");
    7.     Task.RunInMainThread(() =>
    8.     {
    9.         Debug.Log("Run in main thread");
    10.     });
    11. });
    12.  
     
    Last edited: Jan 24, 2017
  44. aaronjbaptiste

    aaronjbaptiste

    Joined:
    Dec 4, 2012
    Posts:
    62
    Ah you can pass a ThreadPool into a Task and it will use it. No need to Queue your own actions. All good.
     
  45. aaronjbaptiste

    aaronjbaptiste

    Joined:
    Dec 4, 2012
    Posts:
    62
    Quick tip for people looking to boost performance, I found the default number of Threads (100) to be too many for me, I needed to run hundreds of tasks in parallel in as quick a time as possible. I found setting maxThreads in the ThreadPool to:

    Code (CSharp):
    1. Mathf.Max(1, Mathf.FloorToInt(SystemInfo.processorCount / 2));
    To be the sweet spot. It might need some tweaking for production use, as SystemInfo.processorCount assumes the CPU has double the amount of VirtualCores as it has PhyiscalCores. In my case my CPU has 8 virtual cores and 4 physical cores, running a max of 4 threads means zero thread switching on my machine. This setting cut my processing down from 11 seconds to 3.5 seconds.
     
  46. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    Thanks @aaronjbaptiste !

    About your "Task.Any" question:

    Code (CSharp):
    1. Task.WhenAny (tasks).ContinueInMainThreadWith ((t) => {
    2.     DoSomethingWithTheResult(t);
    3. });
    4.  
    Where task is a list of task you want to run
     
  47. belveder_79

    belveder_79

    Joined:
    Jul 26, 2016
    Posts:
    15
    Hi,
    I'm searching for some library to do multithreading on UWP platforms, but the somewhat castrated .NET framework lacks certain functions. Any insights on the usability of your lib for UWP Windows 10 (Hololens)?
    Best...
     
  48. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    Hi @belveder_79
    I'm currently working to give support for UWP builds... it should be ready in a couple of days!
     
  49. KnuckleCracker

    KnuckleCracker

    Joined:
    Dec 27, 2011
    Posts:
    80
    I must be missing something obvious. I want to do the following:
    Code (CSharp):
    1.  
    2. void FixedUpdate() {
    3.    //Do some stuff
    4.        
    5.    List<Task> taskList = new List<Task>();
    6.    taskList.Add(Task.Run(() => { Expensive1();}));      
    7.    taskList.Add(Task.Run(() => { Expensive2();}));      
    8.    taskList.Add(Task.Run(() => { Expensive3();}));      
    9.  
    10.    //Wait on all three threads above to finish
    11.    //Task.WhenAll(taskList); doesn't actually wait.
    12.    //Task.WhenAll(taskList).Wait((t) => { }); Doesn't wait either.
    13.  
    14.    //Do some more work that must be done only after the above three threads have finished.
    15.    //This work must be done before FixedUpdate() is allowed to complete.
    16. }
    Task.WhenAll and Task.Wait don't actually block or wait. I understand the many uses cases for not blocking and instead using a completion model and a callback. However, the stuff before I create the threads and the stuff after creating the threads must be done in sequence and the stuff afterwards must be done after all threads have completed. All of that has to be done in one game frame. The reason for threading at all is that the expensive work can be broken up and done in parallel. So running with three threads (for instance) in parallel is faster than just calling Expensive1(); Expensive2(); Expensive3(); in sequence.

    So is there a way using Tasks to actually wait/block?
     
  50. arklay_corp

    arklay_corp

    Joined:
    Apr 23, 2014
    Posts:
    242
    @KnuckleCracker that's the whole point, to avoid blocking the main thread! (FixedUpdated is executed in the main thread)

    You whould use:

    Code (CSharp):
    1. Task.WhenAll(taskList).ContinueInMainThreadWith((t) =>
    2. {
    3. // this code will be executed after all the tasks
    4. });
    But keep in mind that FixedUpdate can be executed again while the tasks are still on execution (so you will create the tasks again and you will end up with your tasks duplicated (take a look at BasicTest.cs lines 65-93