从同步方法调用异步方法(Vert.x、Java)

从同步方法调用异步方法(Vert.x、Java),java,multithreading,asynchronous,vert.x,shiro,Java,Multithreading,Asynchronous,Vert.x,Shiro,我们有一组Java应用程序,这些应用程序最初是使用普通的同步方法编写的,但在任何有意义的地方,大部分都已转换为异步Vert.x(常规API,而不是Rx)。我们在同步和异步代码之间的界限上遇到了一些问题,特别是当我们有一个必须是同步的方法(下面解释的推理)并且我们想要从中调用异步方法时 以前关于堆栈溢出有许多类似的问题,但实际上所有这些问题都是在C#上下文中提出的,答案似乎并不适用 除此之外,我们正在使用Geotools和ApacheShiro。两者都通过使用它们定义的严格同步的API进行扩展来提

我们有一组Java应用程序,这些应用程序最初是使用普通的同步方法编写的,但在任何有意义的地方,大部分都已转换为异步Vert.x(常规API,而不是Rx)。我们在同步和异步代码之间的界限上遇到了一些问题,特别是当我们有一个必须是同步的方法(下面解释的推理)并且我们想要从中调用异步方法时

以前关于堆栈溢出有许多类似的问题,但实际上所有这些问题都是在C#上下文中提出的,答案似乎并不适用

除此之外,我们正在使用Geotools和ApacheShiro。两者都通过使用它们定义的严格同步的API进行扩展来提供定制。作为一个具体的例子,Shiro的自定义授权域需要访问我们的用户数据存储,为此我们创建了一个异步DAOAPI。我们必须编写的Shiro方法称为
doGetAuthorizationInfo它应返回
授权信息
。但是,似乎没有可靠的方法从异步DAOAPI的另一端访问授权数据

在特定情况下,线程不是由Vert.x创建的,使用
CompletableFuture
是一个可行的解决方案:同步
DogeAuthorizationInfo
将异步工作推送到Vert.x线程,然后在
CompletableFuture.get()
中阻塞当前线程,直到结果可用

不幸的是,Shiro(或Geotools,或其他)方法可能会在Vert.x线程上调用。在这种情况下,阻止当前线程是非常糟糕的:如果它是事件循环线程,那么我们就违反了黄金法则,而如果它是工作线程(比如说,通过
Vertx.executeblock
),那么阻止它将阻止工作线程从其队列中获取更多内容-这意味着阻止将是永久性的

这个问题有“标准”解决方案吗?在我看来,只要Vert.x在可扩展的同步库下使用,它就会出现。这只是人们避免的情况吗

编辑 。。。再详细一点。下面是org.apache.shiro.realm.authorizationrealm中的一个片段:

/**
 * Retrieves the AuthorizationInfo for the given principals from the underlying data store.  When returning
 * an instance from this method, you might want to consider using an instance of
 * {@link org.apache.shiro.authz.SimpleAuthorizationInfo SimpleAuthorizationInfo}, as it is suitable in most cases.
 *
 * @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
 * @return the AuthorizationInfo associated with this principals.
 * @see org.apache.shiro.authz.SimpleAuthorizationInfo
 */
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
我们的数据访问层有如下方法:

void loadUserAccount(String id, Handler<AsyncResult<UserAccount>> handler);
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    CompletableFuture<UserAccount> completable = new CompletableFuture<>();
    vertx.<UserAccount>executeBlocking(vertxFuture -> {
        loadUserAccount((String) principals.getPrimaryPrincipal(), vertxFuture);
    }, res -> {
        if (res.failed()) {
            completable.completeExceptionally(res.cause());
        } else {
            completable.complete(res.result());
        }
    });

    // Block until the Vert.x worker thread provides its result.
    UserAccount ua = completable.get();

    // Build up authorization info from the user account
    return new SimpleAuthorizationInfo(/* etc...*/);
}

但是如果在Vert.x线程中调用了
doGetAuthorizationInfo
,那么情况就完全不同了。上面的技巧将阻止事件循环线程,因此这是不可能的。或者,如果它是一个工作线程,那么
executeBlocking
调用将把
loadUserAccount
任务放到同一个工作线程的队列中(我相信),因此后续的
completable.get()
将永久阻塞。

我打赌你已经知道答案了,但我们希望不是这样——如果对GeoTools或Shiro的调用需要阻止等待某个响应,那么您不应该在Vert.x线程上进行该调用

您应该创建一个
ExecutorService
,其中包含一个线程池,您应该使用该线程池来执行这些调用,并安排每个提交的任务在完成时发送一条Vert.x消息

在移动到线程池中的块的大小方面,您可能有一些灵活性。您可以将更大的调用移到调用堆栈的更高位置,而不是紧密地包装这些调用。您可能会根据需要更改多少代码来做出此决定。由于使一个方法异步通常意味着无论如何都要更改其调用堆栈中的所有同步方法(这是这种异步模型的一个不幸的基本问题),因此您可能希望在堆栈的高层执行此操作


您可能会得到一个适配器层,该层为各种同步服务提供Vert.x API。

您能用所涉及的接口片段更新您的问题吗?这将更容易形成一个答案
CompletionStage
有一些方法可以在特定的执行器上执行回调,因此这可能是您问题的解决方案。+1我无法回答Vert.x方面的任何问题,但您可以创建一个与Shiro主题关联的线程,如下所示:,然后将其插入您的ExecutorService@MattTimmermans是的,你一针见血。基于这些文档,我的印象是阻塞工作线程是可以的,因此当我们最初编写对GeoTools map render方法的调用时,例如,我像个好孩子一样将其包装在ExecuteBlock调用中。一切都很好,直到我们尝试实现一个ExternalGraphicFactory来渲染地图上的自定义标记。由于这些标记的源代码是通过我们的异步API访问的,因此我们立即解决了这个问题。令人困惑的是,它有时起作用。好吧@Brianders Shiro使用ThreadLocals实际上暴露了一个稍有不同的问题,即Vert.x无法控制线程的创建方式。我们已经通过(本质上)在web请求处理程序中确定当前线程的主题,立即解析所有授权数据,并将其作为方法参数负载携带到异步代码中,从而适应了这一现实。混乱但可行,而且相当简单,因为我们使用GWT和Shiro过滤器。因此,我们不需要您提到的关联技术,但现在我们可能需要。谢谢