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
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Not all code paths return a value when using Task.ContinueWith

Discussion in 'Scripting' started by Sun-Pengfei, Oct 14, 2016.

  1. Sun-Pengfei

    Sun-Pengfei

    Joined:
    Nov 12, 2015
    Posts:
    35
    Hi, I'm using a 3rd party sdk with their SaaS service. Following are some code to query for data on their cloud:

    Code (CSharp):
    1.  
    2. AVObject loadOnline(string className, string id)
    3.     {
    4.  
    5.         AVQuery<AVObject> query = new AVQuery<AVObject>(className);
    6.         query.GetAsync(id).ContinueWith(t =>
    7.         {
    8.             if (!t.IsFaulted)
    9.             {
    10.                 Debug.Log("load succeeds!");
    11.                 AVObject result = t.Result;
    12.                 return result;
    13.             }
    14.             else
    15.             {
    16.                 Debug.Log("load fails!");
    17.                 return null;
    18.             }
    19.         });
    20.     }
    21.  
    These codes works if there's no need to return, but here the Visual Studio reminds me "Not all code paths return a value".

    I tried to google about it, found some posts using "Await" keyword with tasks. While I'm trying to learn "Asynchronous Programming with async and await" (https://msdn.microsoft.com/en-us/library/mt674882.aspx), I found that unity does not support "Await" or "Async" or even Tasks. I'm very confused.

    1 If unity does not support Await, Async and Tasks, then why the 3rd party SDK with Task could work?
    2 What should I do to make my code work?
    3 What's the recommended way to implement features such as reading data online without blocking(freeze) the whole game?
     
  2. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    I think the problem is the ContinueWith is taking a function as an argument so your returning a result from the ContinueWith.. not in the outside code.

    I think if you do it like this:
    Code (CSharp):
    1. return query.GetAsync(id).ContinueWith(t=>
    2.    // your code
    basically the returns inside of ContinueWith are setting the value of GetAsync. but nothing is beign returned in the main function
     
    Last edited: Oct 14, 2016
  3. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    Takatok is right.

    I also want to point out the reason Visual studio is giving you the error is its presuming that the code inside Continuewith may not run, and if that was to occur there would be no return line run. Even if you know it would, visual studio has heart failure. Similar to if you only put a return statement within a while loop.


    Takatok's solution is the best way to resolve it. If for any reason you can't/wont do it that way, simply putting a 'return null' inbetween line 19 and 20 would also work. Don't do it that way though, its more to illustrate why the error message comes up.
     
  4. Sun-Pengfei

    Sun-Pengfei

    Joined:
    Nov 12, 2015
    Posts:
    35
    @takatok @absolute_disgrace
    Thanks for your advice, but now it says "Cannot implicitly convert type ‘System.Threading.Tasks.Task<LeanCloud.>AVObject’ to ‘LeanCloud.AVObject’ "
     
  5. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    You have two functions here.

    This one does not return a AVObject through any of its code paths.

    Code (CSharp):
    1.     AVObject loadOnline(string className, string id)
    2.     {
    3.         AVQuery<AVObject> query = new AVQuery<AVObject>(className);
    4.         query.GetAsync(id).ContinueWith(...);
    5.     }
    The internal function does return a value, but you are not doing anything with the result. So there really is no point anyway.

    Code (CSharp):
    1.         (t =>
    2.         {
    3.             if (!t.IsFaulted)
    4.             {
    5.                 Debug.Log("load succeeds!");
    6.                 AVObject result = t.Result;
    7.                 return result;
    8.             }
    9.             else
    10.             {
    11.                 Debug.Log("load fails!");
    12.                 return null;
    13.             }
    14.         }
    Its worth noting that ContinueWith is normally the wrong way to approach async functions in Unity. You will start to get errors about the main thread if you do anything requiring the Unity API in ContinueWith. Coroutines are better. A typical way to set this up in Unity is as follows.

    Code (CSharp):
    1.     AVObject result;
    2.  
    3.     IEnumerator LoadOnline(string className, string id)
    4.     {
    5.         AVQuery<AVObject> query = new AVQuery<AVObject>(className);
    6.         while (!query.isDone) yeild return null;
    7.         if (!t.IsFaulted)
    8.         {
    9.             Debug.Log("load succeeds!");
    10.             result = t.Result;
    11.         }
    12.         else
    13.         {
    14.             Debug.Log("load fails!");
    15.             result = null;
    16.         }
    17.     }
     
  6. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    Try:
    Code (CSharp):
    1. AVObject loadOnline(string className, string id)
    2.     {
    3.         AVObject result;
    4.  
    5.         AVQuery<AVObject> query = new AVQuery<AVObject>(className);
    6.         query.GetAsync(id).ContinueWith(t =>
    7.         {
    8.             if (!t.IsFaulted)
    9.             {
    10.                 Debug.Log("load succeeds!");
    11.                 result = t.Result;
    12.             }
    13.             else
    14.             {
    15.                 Debug.Log("load fails!");
    16.                 result = null;
    17.             }
    18.         });
    19.         return result;
    20.     }
     
  7. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    query is an AVQuery<AVObject> type. I am not familiar with whatever package you are using. But can you get at the AVObject inside query. Like:
    query.AVObject or something like that?

    If you can take out the return line I suggested and after your ContinueWith code at then end of the function just return:
    query.WheverTheAVObjectis
     
  8. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    This will return null every time. You haven't worked with a task library before have you? The point is the code inside continue with doesn't execute until seconds or minutes later. Where as the code after continue with will execute immediately.

    I'm pretty sure its just a wrapper for the standard C# Threading.Tasks library. Its not included in Unity by default, so many back end services include it in their Unity packages.

    You still haven't got to the root of the problem, at the time the outer function returns, there is no value of AVObject. That's kind of the point of using an Async system.
     
  9. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    No i haven't. I was mainly addressing the Visual Studio warning.
     
  10. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Its a pretty nifty library. It basically lets you do async tasks without having to worry about managing the thread pool on your own. Tasks just get assigned whatever thread is idle, or create a new thread if the pool is busy.

    But as with all threading in Unity, you have to do some gymnastics to get back onto the main thread before you can use the Unity API.
     
  11. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    That is pretty nifty! I still have a tonne of unity specific libraries and tricks i need to learn. So much unity, so little hobby time. At least i program for my main work so most of my learning is mainly centered around unity specific tricks and quirks.
     
    Kiwasi likes this.
  12. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    You won't see Tasks much since it's not included by default. It's a C# library, not a Unity one. Really only comes up if you have a package API that uses it. It's reasonably common in back end services.

    But the general coroutine trick I showed earlier is a good one to have up your sleeve.

    It's also useful to be familiar with Lambda syntax for anonymous delegates. If you rewrite the OPs code out with regular delegates it becomes super obvious why the compiler threw an error.
     
    absolute_disgrace likes this.
  13. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Yah. thats why I was having him return the query directly, but seems like there needs to be some typecasting or something else to get it to work correctly with the delegate function. But just throwing it into a Coroutine does seem easiest.
     
    Kiwasi likes this.
  14. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Got ya. You could return the query object and wait for it somewhere else in code.

    But I wouldn't go near the result until the query is done. Best case scenario you get a runtime error. Worst case you are throwing around references to a half initialised object in an undefined state.
     
  15. Sun-Pengfei

    Sun-Pengfei

    Joined:
    Nov 12, 2015
    Posts:
    35

    @BoredMormon I think you pretty much got the idea here. I'm not familiar with yield but posted your code on the official forum of that SaaS service and waiting for their reply. Thanks a lot, I'll try these code once I figured out what it means.
     
  16. Sun-Pengfei

    Sun-Pengfei

    Joined:
    Nov 12, 2015
    Posts:
    35
    While trying your code, I have new problems:
    1 query does not not have isDone property.
    2 query.GetAsync(id) should be called to actually start the query action.
     
  17. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    What type does query.GetAsync(id) return? Does that have an isDone property? Or an isComplete, isFinished ect.

    Calling GetAsync to start a query sounds like a bad implementation to me. I would expect to see a Start or Run method.
     
  18. Sun-Pengfei

    Sun-Pengfei

    Joined:
    Nov 12, 2015
    Posts:
    35
    t.IsCompleted, t.IsCanceld, t.IsFaulted are the status that you could use. But that also means your can only access them within the inner function in continueWith block right?

    GetAsync() returns a Task<T>. It's said that leancloud SDK has implemented their only Task system that looks very similar to the Task in .Net 4.5.
     
  19. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    So save the return value from GetAsync and the isCompleted from that within the while loop.
     
  20. Sun-Pengfei

    Sun-Pengfei

    Joined:
    Nov 12, 2015
    Posts:
    35
    Could you give me some example code where should I put the GetAsync in?
    I plan to re-read and study the whole document of the SDK tomorrow, will update this post soon. Thanks for your help again!
     
  21. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Something like this. Its just a couple of minor mods to what I posted before.

    Code (CSharp):
    1.     AVObject result;
    2.  
    3.     IEnumerator LoadOnline(string className, string id)
    4.     {
    5.         AVQuery<AVObject> query = new AVQuery<AVObject>(className);
    6.         var task = query.GetAsync(id);
    7.         while (!task.isComplete) yeild return null;
    8.         if (!task.IsFaulted)
    9.         {
    10.             Debug.Log("load succeeds!");
    11.             result = task.Result;
    12.         }
    13.         else
    14.         {
    15.             Debug.Log("load fails!");
    16.             result = null;
    17.         }
    18.     }
     
  22. Sun-Pengfei

    Sun-Pengfei

    Joined:
    Nov 12, 2015
    Posts:
    35
    Thanks! I got the idea behind the codes you provided, and I think it'll work. I'm still studying the SDK documentation. Probably I'll re-design the strategy of the whole fetching data process. The original idea of return the data is just wrong, because after all this is an asynchronous method. I'm thinking to have the caller provide a function delegate. Let this method return void, and call the delegate function when works are done.
     
    Kiwasi likes this.
  23. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    That sounds about right.

    Just remember you also need to get back on the main thread if you are touching the Unity API
     
  24. Sun-Pengfei

    Sun-Pengfei

    Joined:
    Nov 12, 2015
    Posts:
    35
    I'm not familiar with thread related coding. And I also heard that Unity doesn't support thread related stuff. How do you know if you're on the main thread?
     
  25. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Anything called from a Unity function is on the main thread

    Anything called with a thread you create is not on the main thread.

    Anything called from ContinueWith is also not on the main thread
     
  26. Sun-Pengfei

    Sun-Pengfei

    Joined:
    Nov 12, 2015
    Posts:
    35
    What if for example Unity main scene has a functionA and functionB, functionA calls the async functionC I wrote(the one we mainly discussed), passes a delegate of functionB to functionC, then in functionC there's a ContinueWith, within the ContinuewWith calls the delegate of functionB. Does this delegate of functionB run on main thread?

    BTW since unity doesn't support threading, why the 2nd and 3rd situations exist?
     
  27. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Anything called in continue with, including delegates, is run on a second thread. The typical way to get back to the main thread is to have the main thread check on some frequency if the task is completed. Then have the main thread call your delegate.

    Unity does support threading. You can spin up as many threads as you like. What isn't supported is threadsafe access to the UnityEngine API.
     
  28. Sun-Pengfei

    Sun-Pengfei

    Joined:
    Nov 12, 2015
    Posts:
    35
    I tested all we've discussed. All you said are correct, meaning the idea of using delegate will not work here. I need to come up with a new structure to achieve the fetch-data-on-need strategy.
     
  29. Sun-Pengfei

    Sun-Pengfei

    Joined:
    Nov 12, 2015
    Posts:
    35
    Update:
    I've re-designed the structure, and just finished testing and debugging. All work good so far. The final solution is just as @BoredMormon suggested, constantly check the data query status that you need on main thread.

    @BoredMormon Thanks a lot for your help!