从必要的同步方法中调用异步方法(Vert.x,Java)

时间:2019-03-20 01:13:13

标签: java multithreading asynchronous vert.x shiro

我们有一组Java应用程序,这些Java应用程序最初是使用普通的同步方法编写的,但是在任何有意义的地方都已很大程度上转换为异步Vert.x(常规API,而不是Rx)。在同步代码和异步代码之间的边界上遇到了一些麻烦,特别是当我们有一个必须同步的方法(原因在下文中说明)并且希望从中调用异步方法时。

之前在Stack Overflow上也有很多类似的问题,但实际上所有问题都在C#上下文中,答案似乎并不适用。

我们正在使用Geotools和Apache Shiro。两者都使用严格定义的API通过扩展提供自定义。作为一个具体示例,我们为Shiro定制的授权领域需要访问我们的用户数据存储,为此我们创建了一个异步DAO API。我们必须编写的Shiro方法称为doGetAuthorizationInfo;,预期它会返回AuthorizationInfo。但是似乎没有可靠的方法可以从异步DAO API的另一端访问授权数据。

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

不幸的是,可以在Vert.x线程上调用Shiro(或Geotools或其他工具)方法。在那种情况下,阻塞当前线程是非常糟糕的:如果是事件循环线程,那么我们就违反了黄金法则;而如果它是工作线程(例如,通过Vertx.executeBlocking),那么阻塞它将会阻止工人从队列中捡拾更多东西-这意味着封锁将是永久的。

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

编辑

...还有更多细节。这是org.apache.shiro.realm.AuthorizingRealm的片段:

/**
 * 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);

如何从前者中调用后者?如果我们知道doGetAuthorizationInfo在非Vert.x线程中被调用,那么我们可以做这样的事情:

@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()将永久阻塞。

1 个答案:

答案 0 :(得分:3)

我敢打赌,您已经知道答案了,但是希望不是这样-如果对GeoTools或Shiro的呼叫将需要阻止等待某些响应,那么您不应该在Vert.x线程。

您应该使用线程池创建一个<input type="date">,用于执行这些调用,安排每个提交的任务在完成后发送Vert.x消息。

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

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

相关问题