【问题标题】:How do I return to the main thread from a CompletableFuture如何从 CompletableFuture 返回主线程
【发布时间】:2018-12-15 02:45:25
【问题描述】:

我需要从CompletableFuture<Void> 返回主线程。

我有一个CompletableFuture 的网络,它运行异步以处理接收并插入到 SQL 数据库中的数据。然而,一旦所有这些都完成了,我想返回主线程并执行一个函数,我没有找到合理的方法。

调用join()get() 只会阻塞CompletableFuture 正在运行的线程...我也不想连续轮询isDone()

我尝试使用whenComplete(),但它似乎仍然无法按预期工作......

public static void updatePermIconCache() {
    Logger.debug("thread1: " + Thread.currentThread().getId());
    hubDatabase.getLobbies().thenAccept(s -> {
        for (String lobby : s) {
            for (int i = 0; i < 27; i++) {
                final int x = i;
                hubDatabase.isInPermTable(lobby, i).thenAccept(b -> {
                    if (!b) return;
                    hubDatabase.cachePermToJoinTable(lobby, x);
                });
            }
        }
    }).whenComplete((v, t) -> registerPanels());
}

我希望我的 registerPanels() 函数在主线程中被调用,但考虑到即使函数 updatePermIconCache() 被称为异步,以及确定是否调用此函数的其他函数,我不明白如何,好吧,让它回到主线程。

编辑: 把代码改成这个...

public static void updatePermIconCache() {
    Logger.debug("thread1: " + Thread.currentThread().getId());
    hubDatabase.getLobbies().thenAccept(s -> {
        for (String lobby : s) {
            for (int i = 0; i < 27; i++) {
                final int x = i;
                hubDatabase.isInPermTable(lobby, i).thenAccept(b -> {
                    if (!b) return;
                    hubDatabase.cachePermToJoinTable(lobby, x).join();
                    registerPanels();
                });
            }
        }
    });
}

问题是...主线程 id 是21,在函数registerPanels() 的第一行我打印了Thread#currentThread#getId(),它打印了多个不同的数字,范围在3042 之间。如果它在主线程上运行,它应该打印21,对吗?

编辑: 这是第一个被调用的异步运行方法

public CompletableFuture<Void> calibrate() {
    return CompletableFuture.runAsync(() -> {
        getLobbies().thenAccept(a -> Arrays.stream(a).forEach(this::createPermToJoinTable));
        getLobbies().thenAccept(a -> Arrays.stream(a).forEach(this::createVanishedTable));
    });
}

public CompletableFuture<String[]> getLobbies() {
    String sql = "SELECT * FROM lobby";
    List<String> list = new ArrayList<>();
    return queryAsync(r -> {
        while(r.next()) {
            list.add(r.getString(1));
        }
        return r;
    }, sql).thenApplyAsync(v -> list.toArray(new String[list.size()]));
}

这些方法最初是从这里调用的...

public static CompletableFuture<Void> addToPerm(String lobby) {
    HubDatabase hubDatabase = MinelightHub.getHubDatabase();
    return hubDatabase.calibrate().thenRunAsync(() -> {
        hubDatabase.addToPermToJoinTable(lobby, 0, "Admin", getDesc(true), Material.WOOL, getData(DyeColor.RED), Permissions.ADMIN, true);
        hubDatabase.addToPermToJoinTable(lobby, 1, "Mod", getDesc(false), Material.WOOL, getData(DyeColor.ORANGE), Permissions.MOD, false);
        hubDatabase.addToPermToJoinTable(lobby, 2, "Builder", getDesc(false), Material.WOOL, getData(DyeColor.CYAN), Permissions.BUILDER, false);
        hubDatabase.addToPermToJoinTable(lobby, 3, "YouTube", getDesc(false), Material.WOOL, getData(DyeColor.RED), Permissions.YOUTUBE, false);
        hubDatabase.addToPermToJoinTable(lobby, 9, "JrAdmin", getDesc(false), Material.WOOL, getData(DyeColor.RED), Permissions.JRADMIN, false);
        hubDatabase.addToPermToJoinTable(lobby, 10, "JrMod", getDesc(false), Material.WOOL, getData(DyeColor.ORANGE), Permissions.JRMOD, false);
        hubDatabase.addToPermToJoinTable(lobby, 11, "Artist", getDesc(false), Material.WOOL, getData(DyeColor.CYAN), Permissions.ARTIST, false);
        hubDatabase.addToPermToJoinTable(lobby, 12, "Twitch", getDesc(false), Material.WOOL, getData(DyeColor.PURPLE), Permissions.TWITCH, false);
        hubDatabase.addToPermToJoinTable(lobby, 17, "Default", getDesc(false), Material.WOOL, getData(DyeColor.GRAY), Permissions.DEFAULT, false);
        hubDatabase.addToPermToJoinTable(lobby, 18, "SrMod", getDesc(false), Material.WOOL, getData(DyeColor.ORANGE), Permissions.SRMOD, false);
        hubDatabase.addToPermToJoinTable(lobby, 19, "Trial", getDesc(false), Material.WOOL, getData(DyeColor.ORANGE), Permissions.TRIAL, false);
        hubDatabase.addToPermToJoinTable(lobby, 20, "Partner", getDesc(false), Material.WOOL, getData(DyeColor.BLUE), Permissions.PARTNER, false);
        hubDatabase.addToPermToJoinTable(lobby, 21, "VIP", getDesc(false), Material.WOOL, getData(DyeColor.GREEN), Permissions.VIP, false);
    }).thenRunAsync(InventoryManager::updatePermIconCache);
}

现在您可以看到有助于了解这种情况的必要路径。 当我创建一个“大厅”时,#addToPerm(String lobby) 方法开始运行 不过,最初,需要先创建 SQL 数据库中的表,然后才能更改向其中添加任何数据......这就是(在#addToPerm(String lobby) 方法中)#calibrate 方法所做的。一旦它创建了大厅,我需要用数据填充它,例如hubDatabase#addToPermJoinTable()。添加完所有数据后,我需要更新本地缓存InventoryManager#updatePermIconCache()。加载完所有缓存后,我需要返回主线程并调用 #registerPanels() 方法,因为该方法使用线程不安全 API。我仍然坚持如何做到这一点......

【问题讨论】:

  • 这期间主线程在做些什么吗?还是您的意思是 GUI 线程从中进行 GUI 调用?在后一种情况下:您使用的是哪个 GUI(Swing、JavaFX)?
  • 在主线程和CompletableFuture 线程中调用registerPanels 有什么区别?当数据库插入完成时,registerPanels 会在您当前的设置中被调用吗?
  • join() 将阻塞主线程直到 completablefuture 完成
  • 主线程是什么意思? main() 方法在哪里运行?这是一个普通线程,只是第一个线程。
  • 为什么要在“主”线程上运行registerPanels()?它有什么特别之处?好的解决方案需要这些信息与您相关,否则可能只是纯粹的猜测。提供minimal reproducible example 也有助于更好地理解问题。

标签: java multithreading minecraft bukkit completable-future


【解决方案1】:

调用 join() 或 get() 只会阻塞 CompletableFuture 正在运行的线程

所以你是从 current 线程完成那个未来?然后不要这样做...确保在您调用 joinget 的线程之外的线程上执行所有异步操作,并完成它们的未来。

【讨论】:

  • 所以我必须遍历我的异步调用网络,并获得初始的 CompletableFuture 并等待?我宁愿采用更简单的方法 >.> 它是一个由 thenAcceptAsync()'sthenRunAsync()'s 等组成的网络......可能会调用 20-30 个不同的函数
  • 你必须等待 final CompletableFuture - 依赖(正在等待)所有其他的。实际上它不必等待所有其他人,它可以说“好吧,未来A回来了一个结果,所以我不再关心未来B,因为它现在无关紧要了”。但它位于异步调用树的末尾。您可以将其放入变量或 Map 中以跟踪它。你是对的,你不应该遍历树,但通常没有必要,因为我们使用变量。
  • 我试过了,还是不行……更新我的帖子
【解决方案2】:

要返回一个线程(主线程或非主线程,无关紧要),该线程必须调用某个等待给定 CompletableFuture 的方法。等待 CompletableFuture 是通过调用它的方法 get() 来完成的。就是这样。

您担心“调用 join() 或 get() 只会阻塞 CompletableFuture 正在运行的线程......” - 这只会在从调用的方法调用 join() 或 get() 时发生可完成的未来。不要这样做,程序会挂起。只需启动所有 CompletableFutures。然后才调用 join() 或 get()。

由于(如打印的那样)方法 updatePermIconCache 在不同的线程上运行,这意味着它不是从主线程调用的。我们看不到主线程上调用了什么,因此我们无法给您适当的建议。

【讨论】:

  • 我已经更新了我最初的帖子,解释了这个过程
  • @Realmm 您仍然没有解释为什么要在主线程中调用 registerPanels() 函数。 “主线程”是什么意思?它是 Swing 中的 GUI 线程,通常可以通过“SwingUtilities.invokeLater()”访问吗?还是您使用另一种 UI?
  • 主线程...程序运行的线程,我也解释过了,见主帖上的cmets
  • @Realmm “主线程......程序运行的线程” - 这是一个误解。该程序在每个线程上运行,而不仅仅是在主线程上。 “我确实解释了,请参阅主帖上的 cmets” - 不,你没有。您只对提到主线程的主帖子发表了一条评论:“据报道,API 是线程不安全的,所以我试图在主线程上调用它”,它没有解释主线程是什么。是的,处理线程不安全 API 的方法之一是从专用线程访问它,但是这个线程几乎不能称为 main。
  • 那么,当你使用 Thread#sleep 时,如果“主线程”分布在每个线程上,为什么它会停止程序?不管怎样,你知道我的意思,我只是想找到解决我问题的方法。
【解决方案3】:

在主类实现多线程时调用您的服务方法。 CompletableFuture 的 get() 方法将确保只有在执行完所有线程后,它才会返回到主线程所在的 main 方法,并且只有在 get() 方法返回后才会执行。

//Create a list of CompletableFutures:
List<CompletableFuture> futures= new ArrayList<>();

//Add your futures in this arrayList as mentioned below :
futures.add(CompletableFuture.runAsync(() ->{   
        // your code
});

CompletableFuture<Void> allCfs=CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));

try 
{
allCfs.get();
} catch (InterruptedException | ExecutionException e) {
        log.error(e.getMessage());
} 

//Once this process will be completed, only then your main thread will get executed.

Main Thread()

【讨论】:

  • 添加一行来解释这段代码 sn-p 的帮助
  • 在主类实现多线程的地方调用你的服务方法。 CompletableFuture 的 get() 方法会确保只有在所有线程执行完毕后,才会返回到主线程所在的 main 方法,并且只有在 get() 方法返回后才会被执行。
  • 所有线程单独运行,有两种 CompletableFutures。第一个被所有线程使用,即 for 循环内的 CompletableFuture.runAsync(() ,第二个是所有这些线程的 CompletableFuture 的列表。get() 方法正在第二个 CompletableFuture (这是一个列表)上调用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-10-24
  • 1970-01-01
  • 2016-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-18
相关资源
最近更新 更多