Returning Value From Async Task

Discussion in 'Plugin Development' started by mike546378, Jul 7, 2014.

Thread Status:
Not open for further replies.
  1. Hello, I have a method that executed a mySQL query and returns the value. I decided to make the method Asynchronous to avoid lag in the main thread however I am running into trouble returning the value
    Here is the method right now:
    (http://pastebin.com/ZcB7uYin)
    Code:java
    1.  
    2. public static HashMap<Player,Long> TimePlayed = new HashMap<>();
    3. public static HashMap<Player,Integer> TimePlayedTimer = new HashMap<>();
    4. public static Long getTimePlayed(final Player p){
    5. Bukkit.getScheduler().runTaskAsynchronously(plugin, new BukkitRunnable(){
    6. @Override
    7. public void run() {
    8. try{
    9. PreparedStatement ps = plugin.MySQL.openConnection().prepareStatement("SELECT playtime FROM users WHERE UUID='"+p.getUniqueId().toString()+"'");
    10. ResultSet rs = ps.executeQuery();
    11. if(!rs.next()){
    12. Stats.playtime.put(p, 0L);
    13. }
    14. Long playtime = rs.getLong("playtime");
    15. rs.close();
    16. Stats.playtime.put(p, playtime);
    17. }catch(Exception ex){
    18. ex.printStackTrace();
    19. }
    20. Stats.playtime.put(p, 0L);
    21. }
    22. });
    23.  
    24. TimePlayedTimer.put(p,Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new BukkitRunnable(){
    25. @Override
    26. public void run(){
    27. if(TimePlayed.get(p) != null){
    28. Bukkit.getScheduler().cancelTask(TimePlayedTimer.get(p));
    29. TimePlayedTimer.remove(p);
    30. Long timeplayed = TimePlayed.get(p);
    31. TimePlayed.remove(p);
    32. /**
    33. * This is the bit I am having issues with
    34. */
    35. return timeplayed;
    36. }
    37. }
    38. }, 0, 2));
    39. }
    40.  


    The issue being that if I return the value of the query directly after starting the Async task then it returns null since the task has not finished yet.
    I have been trying to make this work all day, any help is appreciated
     
  2. Offline

    mythbusterma

    You can't return right out of the async method, it won't work.

    You'll probably want to use the Runnable and Future. Create a new RunnableFuture<V> (where V is the type you would like to return) and maintain a reference to it, inside of it make sure to implement the run(), get(), and isDone() methods. The run() method is extended from Runnable and serves the same purpose, the get() method will return an object of type V, and the isDone() method is used to ask the Future if it is ready to have the get() value taken from it. Then, you can pass it to the BukkitScheduler via runTaskAsynchronously(Plugin,Runnable (in this case RunnableFuture)).

    With the instance you've maintained, you can ask RunnableFuture#isDone() from the main thread periodically to see if it is done, and if it is, call RunnableFuture#get() to get the value you need.

    If this is unclear, just ask for some clarification.
     
  3. Offline

    fireblast709

    I personally prefer an interface Callback with the code that has to be executed right after. In my opinion a bit cleaner than the Callable-Future scheme
     
    mythbusterma and bennie3211 like this.
  4. Offline

    mythbusterma

    Of course you can do this also.

    One more option is to use the BukkitScheduler to schedule a task asynchronously that will be run synchronously

    E.g.:

    Code:java
    1. public void doAsyncThing(final String string) {
    2. final BukkitScheduler scheduler = Bukkit.getServer().getScheduler();
    3. scheduler.runTaskAsynchronously (plugin, new BukkitRunnable () {
    4. @Override
    5. public void run() {
    6. StringBuilder sb = new StringBuilder(string);
    7. for(int i = 0; i < 1000; i++) {
    8. sb.append("hi");
    9. }
    10. scheduler.runTask(plugin, new Runnable () {
    11. @Override
    12. public void run() {
    13. plugin.doSomethingWith(string);
    14. }
    15. });
    16. });
    17. }
    18.  
     
  5. mythbusterma fireblast709 Thank you for both the suggestions, I will try these methods tomorrow and get back to you if I still cant get it working. Appreciate the help here.

    mythbusterma Hey, so I was looking at your methods and your first suggestion does essentially the same thing as my existing code it seems. In my code above lines 24 - 38 is a syncRepetingTask periodically checking if the HashMap is populated which would indicate that the asyncTask is finished and allow me to get the value. The problem I am having is returning this value to whatever called the method since the method returns a Long. I cant find any way to return it, even from a syncRepeting task. Seems both your ways of doing it would run into the same problem, any suggestions?
    Never used the method that fireblast709 suggested but would this solve the problem or does it still have the same issues?

    Thanks

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 9, 2016
  6. Offline

    fireblast709

    Let me create a more flexible example. Let's go and fetch a boolean.
    Code:java
    1. public interface Callback<T>
    2. {
    3. public void execute(T response);
    4. }
    Code:java
    1. public void doAsyncGetBoolean(final UUID uuid, final Callback<Boolean> callback)
    2. {
    3. new BukkitRunnable()
    4. {
    5. @Override
    6. public void run()
    7. {
    8. boolean ret = false;
    9. //TODO: All the MySQL stuff and what not
    10. new BukkitRunnable()
    11. {
    12. @Override
    13. public void run()
    14. {
    15. callback.execute(ret);
    16. }
    17. }.runTask(plugin);
    18. }
    19. }.runTaskAsynchronously(plugin);
    20. }

    Code:java
    1. Callback<Boolean> callback = new Callback<Boolean>()
    2. {
    3. public void execute(Boolean b)
    4. {
    5. // Do whatever with the boolean
    6. }
    7. }; // <- note the ; right here, since we assign it
    8. // to a variable instead of instantly dumping it in a method.
    9. // (Just for readability)
    10. doAsyncGetBoolean(uuid, callback)
     
    KyleLazo likes this.
  7. fireblast709 Thanks but how would I then return the value to wherever doAsyncGetBoolean is being called from?
    I am trying to use http://pastebin.com/GkME6uhh but cant call the method. Sorry for basically being a noob, not used Callbacks before and thanks for the help
     
  8. Offline

    mythbusterma

    Think about what you're trying to do here, you cannot simply "return" a value from another thread without blocking, it isn't possible. You have create code that either 1. does something on the main thread to indicate that it has finished 2. have to periodically check the worker thread from the main thread for completion.

    Also, make your HashMaps private, not static, and do not store an reference to a Player in them, instead use the Player's name. Furthermore, HashMaps are not threadsafe and should not be used as if they are, like you are.
     
  9. Offline

    xTigerRebornx

  10. That's what I was originally attempting to do with
    Code:java
    1. TimePlayedTimer.put(p,Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new BukkitRunnable(){
    2. @Override
    3. public void run(){
    4. if(TimePlayed.get(p) != null){
    5. Bukkit.getScheduler().cancelTask(TimePlayedTimer.get(p));
    6. TimePlayedTimer.remove(p);
    7. Long timeplayed = TimePlayed.get(p);
    8. TimePlayed.remove(p);
    9. return timeplayed;
    10. }
    11. }
    12. }, 0, 2));

    in the first post which should tell me when the Async task is complete however I ran into the problem of how to then return the value from this syncRepetingTask after the aSyncTask is complete unless ofcource this is completely wrong. This is one of the first times I am really experimenting with other threads within one of my plugins
    Thanks for the tip, will keep that in mind
     
  11. Offline

    mythbusterma

    But you can't "return" from that function, you must query it and act upon the data. By waiting for that function to return, you will block execution of the main thread. It's just not as a simple as returning the value.
     
  12. Offline

    fireblast709

    mike546378 last snippet, line 5. Read the commentary ;)
    mythbusterma Guava's MapMaker allows you to create a thread safe WeakHashMap
     
  13. Offline

    mythbusterma

    Or you can use java.util.ConcurrentHashMap.....which doesn't require any external code (that being said, ConcurrentHashMaps are EXTREMELY expensive apparently).
     
  14. Ok, so what I have been asking simply can not be done then; Execute a function from main thread -> have the code run in 2nd thread -> have it return the outcome of the code to the main thread.

    I really hate having to ask this and I know it is frowned upon but is there any chance you would be able to provide the code for me to execute the code within this class Asynchronously while being able to retrieve the result of the query from any other class (or other plugin) at any given time.
    This is the first time in the last year of plugin development that I have straight up asked for code but sometimes the best way to learn is just to stick some working code in the plugin and analyse how it works.
    I understand if you do not want to provide the code though which is fine. I would forever be grateful though.

    Failing that plea for help, which of the suggestions above would you recommend for what I am trying to accomplish


    Yes but how would I know where to send the value back to? The method is meant to be called from multiple different classes and different plugins

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 9, 2016
  15. Offline

    xTigerRebornx

    mike546378 Use the scheduler to your advantage, schedule an async task that fetches the data, then from that task schedule a sync task that handles what you want to do with the play time.
    A pseudo-code example:
    You want to send the Player a message with his play time.
    have a method that, rather then fetching the data and returning it, sends the message.
    Method would go like this:
    schedule async task:
    task makes call to fetch data from database
    after that call finishes, schedule sync task that calls sendMessage() with the time in it

    or
    cache the values locally, instead of fetching the data on demand, fetch it on startup, push the data stored in memory to the database every so often to prevent severe data loss, and use/update the data in memory rather then grabbing it every time
     
  16. Your second idea there could work, going to try it.
    The issue with your first idea is that I am programming for a minigames server with a database that stores every players stats and each minigame will have its own plugin so ideally I wanted the other minigame plugins to just use the methods within this plugin to get the players stats instead of having to open a new database connection within each of the other plugins if that makes any sense. Just keeps the code shorter and tidier.
     
  17. Offline

    fireblast709

    mythbusterma likes this.
Thread Status:
Not open for further replies.

Share This Page