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

问题描述 投票:1回答:1

我们有一组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);

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

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

java multithreading asynchronous vert.x shiro
1个回答
3
投票

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

您应该创建一个带有线程池的ExecutorService,您应该使用它来执行这些调用,安排每个提交的任务在完成后发送Vert.x消息。

您可以在移动到线程池中的块大小方面具有一定的灵活性。您可以在调用堆栈的上方移动更大的内容,而不是紧紧包装这些调用。您可能会根据需要更改的代码来做出此决定。由于使方法异步通常意味着改变其调用堆栈中的所有同步方法(这是这种异步模型的不幸基本问题),您可能希望在堆栈上做得很高。

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

© www.soinside.com 2019 - 2024. All rights reserved.