Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

How to run long synchronous metod asynchronously?

Discussion in 'Scripting' started by KubekSzklany, Mar 9, 2020.

  1. KubekSzklany

    KubekSzklany

    Joined:
    Mar 9, 2020
    Posts:
    9
    Hello. Can someone help me with sync and async method? I tried to run long sync metod, it does not matter what, let's say, that it is function named Test, it is sync and it takes 20-30 seconds. How to run in in another thread/task and not freeze unity? I tried Test(); and it complete properly, but freezes whole unity for 20-30 seconds, I also tried Task.Run(() => Test()); and it not freezes unity, but function Test not worked properly and also IEnumerator Test(); and StartCoroutine(Test()); but it works similar to first try, because I cant use yield return anywhere. So how to run this function in unity?
     
  2. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,748
    IEnumerators are still run on the main thread, so they'll still block while it's running. You need to use System.Threading.

    Fortunately it's not too complicated to use threads. Something like:

    Code (csharp):
    1. using System.Threading;
    2.  
    3. ...
    4.  
    5. void Start() {
    6. Thread t = new Thread( ThreadedCode);
    7. t.Start();
    8. }
    9. public string someResult = "waiting";
    10.  
    11. void ThreadedCode() {
    12. //do your heavy work here
    13. someResult = "done";
    14. }
    You can set variables and stuff from the thread, which you can use to return your results. You can also set a progress value this way to indicate how far along it is, showing a progress bar, etc.
     
  3. KubekSzklany

    KubekSzklany

    Joined:
    Mar 9, 2020
    Posts:
    9
    Ok, I tried your thread method and it works similar to task, but task wont display errors in console, which thread displays. So I assume, that both ways work pretty same. The problem is that now I have "can only be called from the main thread" error. And it is interesting, that my function is doing the same action on first and last line, and first works but last dont. Here is my code:
    Code (CSharp):
    1. Log("Test A"); // Works good
    2. LongFunction(); // 20-30 seconds
    3. Log("Test B"); // Error here
    Log is my own function to log something to file. And when i run this code on task or thread, log A work perfectly, but log B gives me above error when it reading persistent data path. I know that i can assign persistent data path to variable and it probably resolve problem, but in future there can be more difficult function, which cant be resolved that way.
    I hope you understand me. :)
     
  4. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,748
    I think you may need to at this point describe in more detail what you're doing. In particular, what is it you're trying to do in the thread? There are some Unity things that are not thread-safe and simply can't be threaded, so if your bulk processing relies heavily on Unity classes you might need to find a different way.

    On the other hand, if your heavy processing is independent and you just need to do a little "applying the changes" at the end, you can do something like:
    Code (csharp):
    1. private System.Action mainThreadCallback;
    2.  
    3. void Update() {
    4. mainThreadCallback?.Invoke();
    5. mainThreadCallback = null;
    6. }
    7.  
    8. // in your thread function
    9. mainThreadCallback = () => {
    10. someUnitycomponent.someValue = yourProcessedValue;
    11. };
     
  5. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,303
    Just use async await, it handles context switching for you, and allows you to write code that looks as intuitive as synchronous code. It's really convenient.

    Alternatively, you can use Task continuations, and have them run on the main thread context by explicitly telling them to run on that context. The code will not look as intuitive, so I don't see a point in using those when async await is possible.

    If you want to return a value from a thread:

    //myResult is on the main thread. Test is run on a different thread. The context switch is automatically handled.
    myStringResult = await Task.Run<string>(() => Test());


    And yes, you will need to be more specific with what you are doing. Certain things can't be accessed from a different thread, like almost everything from unity, for example.
     
    Last edited: Mar 10, 2020
  6. KubekSzklany

    KubekSzklany

    Joined:
    Mar 9, 2020
    Posts:
    9
    Ok, i probably resolved my problem, but i have another question about this tasks and threading. Is it possible to return to main thread? I mean, is it possible to run non thread safe unity function from already running thread?
     
  7. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,748
    Use something like the code I posted in my last comment for this.
     
  8. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    When I want my extra thread to tell the main thread to do something, I often use a ConcurrentQueue and then check it for anything new in Update.

    https://docs.microsoft.com/en-us/do...rrent.concurrentqueue-1?view=netframework-4.8
     
  9. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,303
    That is called Thread Context Switching
    As already stated, async await does this effortlessly.

    Code (CSharp):
    1. public class TestAsync : MonoBehaviour
    2. {
    3.     public GameObject gameobj;
    4.     // Start is called before the first frame update
    5.     void Start()
    6.     {
    7.         RunThreadAsync();
    8.     }
    9.  
    10.     private async void RunThreadAsync()
    11.     {
    12.         Debug.Log("Starting Thread");
    13.         string newGOName = await Task.Run<string>(() =>
    14.         {
    15.             Thread.Sleep(3000);
    16.             return "NewGameObjectName";
    17.         });
    18.         Debug.Log("Finished Thread");
    19.         gameobj.name = newGOName;
    20.     }
    21.  
    22. }
    Alternatively, you can define a continuation and tell it to run on the main unity thread. Async await is just easier, and the code is structured very similar to normal code.

    Here is the same functionality with a continuation told to run on the main unity thread, so that it has access to the GameObject.

    Code (CSharp):
    1. public class TestAsync : MonoBehaviour
    2. {
    3.     public GameObject gameobj;
    4.     // Start is called before the first frame update
    5.     void Start()
    6.     {
    7.         RunTasks();
    8.     }
    9.  
    10.     private void RunTasks()
    11.     {
    12.         Task<string> longTask = Task.Run<string>(() =>
    13.         {
    14.             Thread.Sleep(3000);
    15.             return "NewGameObjectName";
    16.         });
    17.  
    18.         longTask.ContinueWith(t =>
    19.         {
    20.             gameobj.name = t.Result;
    21.         }, TaskScheduler.FromCurrentSynchronizationContext());
    22.     }
    23.  
    24. }
    FromCurrentSynchronizationContext in this case would return the main unity thread. That's the thread we're in when we are declaring those tasks.

    For certain situations you may use a concurrent collection to share data between threads as Joe-Censored has stated, it depends on what you're doing.
     
    Last edited: Mar 11, 2020
  10. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,748
    Your code sample here would just freeze the main thread until the child thread finishes, right? In which case, what is the point of using a thread at all?
     
  11. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,303
    Edit:: (Neither of the examples block the main thread)

    From what you're quoting, it seems like you're talking about the Async Await code?
    It won't block while it's doing the threaded code.
    Async functions are coroutines.
    Await is the yield statement.

    You await Tasks, in this case the Task.Run thread
    When that Task is completed, the remainder of the async function will be scheduled to run.
    Like I said, it allows you to write code that looks synchronous.

    Async functions can also return data, which is nice for a coroutine, and they wrap them in tasks so that they can be awaited.

    Code (CSharp):
    1.  
    2.     void Start()
    3.     {
    4.         CountToTenAsync();
    5.     }
    6.  
    7.     private async void CountToTenAsync()
    8.     {
    9.         Debug.Log("Starting...");
    10.         for (int i = 1; i <= 10; i++)
    11.         {
    12.             Debug.Log(await OneSecondDelayAsync(i));
    13.         }
    14.         Debug.Log("Finished...");
    15.     }
    16.  
    17.     private async Task<int> OneSecondDelayAsync(int i)
    18.     {
    19.         await Task.Delay(1000);
    20.         return i;
    21.     }
    22.  
    All of that code actually runs on the main thread. They are coroutines.

    The equivalent in unity coroutines would be:

    Code (CSharp):
    1.     void Start()
    2.     {
    3.         StartCoroutine(CountToTen());
    4.     }
    5.  
    6.     private IEnumerator CountToTen()
    7.     {
    8.         Debug.Log("Starting coroutine...");
    9.         for (int i = 1; i <= 10; i++)
    10.         {
    11.             yield return StartCoroutine(OneSecondDelay());
    12.             Debug.Log(i);
    13.         }
    14.         Debug.Log("Finished coroutine...");
    15.     }
    16.  
    17.     private IEnumerator OneSecondDelay()
    18.     {
    19.         yield return new WaitForSeconds(1);
    20.     }
     
    Last edited: Mar 11, 2020
  12. KubekSzklany

    KubekSzklany

    Joined:
    Mar 9, 2020
    Posts:
    9
    Ok, i finally use async await and it works very well. Thank you all for help. But i have one another question, it is possible to use await in if or while? For example while (await VeryLongFunction() == true) { /* code */ }.
     
  13. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,303
    You would have to start a new thread for each iteration of the while loop to feed it new results

    Code (CSharp):
    1.     private async void RunWhileThreadResultTrueAsync()
    2.     {
    3.         Func<bool> threadFunc = () =>
    4.         {
    5.             Thread.Sleep(1000);
    6.             System.Random random = new System.Random();
    7.             int selected = random.Next(1, 12);
    8.             Debug.Log(selected);
    9.             return selected > 2;
    10.         };
    11.  
    12.         while(await Task.Run<bool>(threadFunc))
    13.         {
    14.             Debug.Log("selected > 2");
    15.         }
    16.  
    17.         Debug.Log("selected !> 2 - Exited While");
    18.  
    19.     }
     
    Last edited: Mar 11, 2020
    Daipop likes this.