Solved Returning a value from an async task

Discussion in 'Plugin Development' started by wesley27, Jan 31, 2016.

Thread Status:
Not open for further replies.
  1. Offline

    wesley27

    Hello, I'm using an async thread to perform database queries and avoid lag. I need to get a variable out of those async queries and then return that variable, and I'm really struggling trying to do this.

    I've been reading about this on the forums and google and I understand @fireblast709 example here. That's the only example I've made sense of that does something similar to what I want to do. however, I don't think that will work in my situation -

    I have a method in my main class. This method calls a lookup method in another class. The lookup method does some database lookups asynchronously and pulls out an integer, and then is supposed to return the integer back to the method in the main class. Here's a basic example of what my code looks like and needs to do:
    This would be in the main class of my plugin:
    Code:
    onCommand() {
        int number = otherClass.performLookup();
        //do stuff with and print the variable "number"
    }
    And this would be in the other class with the lookup method:
    Code:
    int number = 0;
    
    public int performLookup() {
        Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
            @Override
            public void run() {
                //Do database lookups and manipulate the variable "number"
            }
        })
        return number;
    }
    I can't figure out how to make it do this. I don't think fireblast's method will work because I need to return an integer variable in that lookup method to my main class, so, calling a callback execute method like @fireblast709 explained won't do the trick. Currently, with the code above, my lookup method is returning the number before the async task is complete.

    If anyone could offer help or advice it'd be greatly appreciated, thanks!
     
  2. Offline

    tobiyas

    The easiest way is to use a simple callback (as fireblast709 mentioned).
    You would do something like this:

    Code:
     
    public void performLookup(final NumberCallback callback, final Player player, final World world, final int id, final boolean syncCallback){
            new BukkitRunnable() {
                @Override
                public void run() {
                    int number = 0;
                    //Do number getting here:
                    //and set it of course!
                    //Do something with the variables: player, world, id.
               
                    //here you call the callback:
                    //This called from the Async Thread!
                    if(!syncCallback)callback.gotNumber(number);
               
               
                    //If you need to call the callback sync (on Mainthread),
                    //use the following:
                    if(syncCallback) {
                        new BukkitRunnable() {
                            @Override
                            public void run() { callback.gotNumber(number); }
                        }.runTask(plugin);
                    }
                }
            }.runTaskAsynchronously(plugin);
        }
        
    That is the method.
    You need the interface declaration of course:

    Code:
        private static interface NumberCallback{
            public void gotNumber(int number);
        }
    At last a small example how to use it:

    Code:
        public void doSomething(final Player player, World world, int id){
            NumberCallback callback = new NumberCallback(){
                @Override
                public void gotNumber(int number) {
                    player.sendMessage(ChatColor.RED + "Fancy number for you: "
                            + number + " on World " + world.getName() + " for ID: " + id);
                }
            };
       
            performLookup(callback, player, world, id, true);
        }
        }

    EDIT: edited to stuff wanted below.
     
    Last edited: Jan 31, 2016
    wesley27 and mythbusterma like this.
  3. Offline

    mythbusterma

    @tobiyas @wesley27

    A callback is a great way of doing this, and I like Tobi's explanation. However, if this needs to be synchronized with something else on the main thread (for example you need two queries to return, or wait for an event), polling is another option.

    With polling, you would have a variable that you can set in the receiving class that is originally set to null (or something like this) and then when information is received, you set the variable to the information. Then, on the main thread, you just occasionally check if the information you're looking for is there. Make sure the data carrier class uses final fields and that the variable you declare is volatile as well.
     
    wesley27 and tobiyas like this.
  4. Offline

    tobiyas

    @mythbusterma That would be the second variant.
    Whereas I personally do not like polling when there is an alternative.
     
    mythbusterma likes this.
  5. Offline

    wesley27

    @tobiyas @mythbusterma Thanks for the responses, I think I'm going to try tobiyas's method. Thanks a lot for explaining out how the code would work, I think I understand the gist of it. However, I'm still a little confused as to how I'm getting the integer back into my main class, and where the methods would be(in which class, I mean).

    Also, I'm not sure if it's more complicated or not because the method that I'm calling from my main class has three parameter variables already. Would that change how this callback is called?

    The thing is, I'm not doing this all in one class. My main class is currently calling a performLookup(player, id, world, time) method that is in a separate class. How does this change what you explained? If I'm correct in my understanding of the callback example you posted, the performLookup(callback, true) is what actually executes the lookup and will return the integer I need. So would I implement this somehow in my main class, and have the rest of the callback code in the other class that does the lookup?
     
  6. Offline

    mythbusterma

    @wesley27

    When you invoke the task, you pass it a Runnable to use as a callback. This Runnable is just like any other class, and it has methods and can access data accordingly. If it is an anonymous inner class, it can access the data of its parent just like a normal BukkitRunnable (which you're already familiar with) can.
     
    wesley27 likes this.
  7. Offline

    tobiyas

    @mythbusterma 100% correct.
    I will edit my example for the variables.
    @wesley27
    EDIT Edited Example above.
     
    wesley27 likes this.
  8. Offline

    wesley27

    @mythbusterma @tobiyas Thanks, it makes more sense now!

    Going over this again, does this make sense?:

    Would I be able to use the two methods and the interface above, having them all be in the same class (my second class with the lookups), and then call the doSomething() method from my main class, having the doSomething() method return the int num = performLookup()?

    Would that work, am I getting this correctly?
     
  9. Offline

    tobiyas

    @wesley27 You do not get a return value on Callbacks. That's the clue of them.
    You have to execute your code in the callback (as @mythbusterma) already said.
    You use a anonyme class (as in my example) to call the method and do the things you need to do with them.
    If you are using Java 8, you can write this even shorter with Lambdas. Just saying.
     
  10. Offline

    wesley27

    @tobiyas Ooohh okay I missed that bit. So, if you have to execute your code within the callback, then what's really the point of the callback? Because, couldn't you just execute the code within the asynchronous method to begin with, doing the same thing with less code?

    That is actually really inconvenient in my scenario, because I have to run this performLookup() method 6 times with different variables, and they are all returned to the main class and put into a series of strings that are then output to the user who enters a command in-game. I didn't mention this earlier because I thought less un-needed details would be more convenient for you.

    But, thinking about that now, if I have to run the code within the callback, I don't even think it's possible to put together strings from variables that are returned from calling the performLookup() method several times. Am I correct in saying this?

    Maybe if I tried the polling thing @mythbusterma was talking about, this would be possible?

    EDIT: Or, is there anyway to just detect when the asynchronous task is complete, and then run my following code after it's done?
     
  11. Offline

    tobiyas

    @wesley27 If you only need it once and it can be done in an async task, then that solution is better of course.
    The possitive part about callbacks are that they can be reused at any other point you need that.
    If you do not need to reuse it, then you probably do not need it.
    It's just a way to keep the code you actually need close to where you actually call it.
    We do not have the insight you have on your code, so we do not know where and how you are using it. So we only make suggestions how it could be done.

    Using @mythbusterma method of sync polling is pretty much the same code except that you have a variable in the first Async Runnable which can be called from outside.
     
  12. Offline

    mythbusterma

    @wesley27

    It's key to understand, as Tobi said, that this isn't technically the same as returning out of a method. When you start the asynchronous task, the method call for that returns (almost) instantly. The idea of a callback is a contract that says "the code in the callback will be executed at some undetermined point in the future at which I have the information that was requested."

    The idea of polling is that instead of the code being run as soon as information is available (which is what a callback is), you constantly check if the information is available, something like this in psuedo-code:

    Code:
    class Holder:
          var data
    
          // run every tick
         onTick():
             if data->isReady():
                  doStuff()
         
         requestData():
              subprocess(
                    // find out what data is
                    data = newData
              )
    You can wait on as much data as you want, alternatively, you can do the concatenation of datasets on the other thread.

    It is helpful to think of callbacks and polling as ways of detecting when the asynchronous task is complete. Callbacks are initiated by the other thread, whereas polling is initiated by the spawning (main) thread. However, they achieve the same end.
     
  13. Offline

    wesley27

    @tobiyas Okay thanks for that explanation, the difference between using it and not makes sense now.

    I'm sorry for not giving up more details on the code earlier, I didn't think it would make a difference (due to my lack of knowledge of how callbacks work). I know you work with what you have and I really appreciate you trying to help.

    So let me try this again, I'll give more information on my code and how I'm actually using it:

    This method is in my main class, and it is called when someone runs a command. (I've commented out unneeded things so its easier to read and to the point):
    Code:
    private void checkGlobal(String name, CommandSender sender, String world, int hours) {
        //get hours, world and id
        try {
            //the parameters in the following method calls are different in each call
            int count1 = OtherClass.performLookup(name, id, world, hours);
            int count2 = OtherClass.performLookup(name, id, world, hours);
            int count3 = OtherClass.performLookup(name, id, world, hours);
            int count4 = OtherClass.performLookup(name, id, world, hours);
            int count5 = OtherClass.performLookup(name, id, world, hours);
            int count6 = OtherClass.performLookup(name, id, world, hours);
            int count7 = OtherClass.performLookup(name, id, world, hours);
            //each count variable is the count of rows in the database that match the used parameters
            //the count variable are then used to do some calculations and then printed out
    
            sender.sendMessage("Notifier: " + ChatColor.GOLD + name);
            sender.sendMessage("-------------------------------");
            sender.sendMessage("#1: " + String.valueOf(count1));
            //continues to print out the rest of the calculations from each count variable
        }
        //handle exceptions and errors
    }
    Now, the performLookup(name, id, world, hours) method is in a separate class, called otherClass in this example of my code:
    Code:
    public class OtherClass {
        int count = 0;
    
        public int performLookup() {
            Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
                @Override
                public void run() {
                    //Do lookups in a database
                    //using a for loop, get the number of rows that match my criteria
                    //and count = this number of rows
                }
            })
            return count;    //i need to return this count
        }
    }
    So, I need to call the performLookup() method several times with different parameters, and get the returned count value back to the main class to perform more actions, which are then compiled into strings that are sent to the player. Is there any way to go about this? Maybe I could somehow use a timer of sorts to return the count variable back to the main class after the async task is done?

    EDIT: @mythbusterma I hadn't refreshed my page, I just saw that. Thanks for that explanation, I understand now. So, I'm thinking I can't use callbacks or polling here.. is there any other way?
     
  14. Offline

    tobiyas

    @wesley27 with that insight, I would say you are good to go with one async task executing everything.
    Something like this:

    Code:
        private void checkGlobal(final String name, final CommandSender sender, final String world, final int hours) {
            new BukkitRunnable() {
               
                @Override
                public void run() {
                    //get hours, world and id 
                    try {
                        //the parameters in the following method calls are different in each call
                        int count1 = performLookup(name, id, world, hours);
                        int count2 = performLookup(name, id, world, hours);
                        int count3 = performLookup(name, id, world, hours);
                        int count4 = performLookup(name, id, world, hours);
                        int count5 = performLookup(name, id, world, hours);
                        int count6 = performLookup(name, id, world, hours);
                        int count7 = performLookup(name, id, world, hours);
                       
                        //each count variable is the count of rows in the database that match the used parameters
                        //the count variable are then used to do some calculations and then printed out
                
                       
                        sender.sendMessage("Notifier: " + ChatColor.GOLD + name);
                        sender.sendMessage("-------------------------------");
                        sender.sendMessage("#1: " + String.valueOf(count1));
                        //continues to print out the rest of the calculations from each count variable
                    }catch(Throwable exp){}
                    //handle exceptions and errors
                }
            }.runTaskAsynchronously(plugin);
        }
    With the PerformLookup method:
    Code:
       
        private int performLookup(String name, int id, String world, int hours){
            if(Bukkit.isPrimaryThread()) throw new IllegalStateException("Running async stuff from Main thread.");
           
            //Do fancy lookup.
            //Lookup stuff...
           
            return 42;
        }
    Do not forget, that when interacting with Bukkit API, you need to schedule stuff to main-thread.
    player.sendMessage also works async (but could change any time).
     
  15. Offline

    wesley27

    @tobiyas Thanks so much, wow, that makes sense and is actually pretty simple! I didn't even think about having the async task begin in my main class, silly me. Thanks!

    I have two more questions then: I notice that in the example above, you wrote the Runnable as this:
    Code:
    new BukkitRunnable() {
        @Override
        public void run() {
            //stuff to run
        }
    }.runTaskAsynchronously(plugin);
    Is there a difference between doing it like that, and doing it like this? Or are they the same? (I just want to be sure)
    Code:
    Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
                @Override
                public void run() {
                    //stuff to run
                }
            });
    And my second question, is: I know it's warned not to use the Bukkit API in an async thread. Even though player.sendMessage() works asynchronously, would it be better/safer to have the sendMessage() calls be in a normal runnable inside the async task?
     
  16. Offline

    tobiyas

    1st: with the BukkitRunnable, you get a BukkitTask back. This is much easier to handle than the taskID from the scheduler.
    Cancle task with BukkitRunnable: task.cancle();
    Cancle task with Scheduler: Bukkit.getScheduler().cancleTask(taskID);
    Also you can easily kill the task itself in the task.
    This is NOT possible with a scheduler, since you get the ID when it started.
    You can not do this with the Scheduler API:

    Code:
            Bukkit.getScheduler().runTaskTimer(plugin, new Runnable() {
               
                @Override
                public void run() {
                    ....
                    //How to cancle from here?!?!?!?
                }
            }, 10, 10);
    It's pretty easy with a BukkitRunnable:
    Code:
            new BukkitRunnable() {
               
                @Override
                public void run() {
                   ....
                    this.cancel();
                }
            }.runTaskTimer(plugin, 10, 10);
    2nd:
    It would be safer to call ALL Bukkit API and interface stuff from sync tasks, except it explicitly sais that it is supported (as AsyncChatEvent for example). It is strongly recomended. Many will throw an error when not called from main thread. Other have to be called from async thread, for example Bukkit.getOfflinePlayer(String).
     
    wesley27 likes this.
  17. Offline

    wesley27

    @tobiyas Alright that makes perfect sense, thank you so so much for helping with all this :)

    I'll reply again after finishing this and testing to say if it is working :)
     
  18. Offline

    tobiyas

    Well good luck then.
     
  19. Offline

    wesley27

    @tobiyas It all works :)

    Thanks again!
     
Thread Status:
Not open for further replies.

Share This Page