在Mono的阻塞线程上执行转换步骤

时间:2019-06-15 11:52:30

标签: java project-reactor reactive-streams

是否有办法确保从将来创建的单个Mono的所有转换步骤都在订阅和阻止的线程上执行?

例如,以下代码

public static void main(String[] args) {
  var future = new CompletableFuture<String>();
  var res = Mono.fromFuture(future).map(val -> {
    System.out.println("Thread: " + Thread.currentThread().getName());
    return val + "1";
  });

  new Thread(() -> {
    try {
      Thread.sleep(1000L);
    } catch (InterruptedException e) {
    }
    future.complete("completed");
  }, "completer").start();

  res.block();
}

打印Thread: completer,因为将来是通过“ completer”线程完成的。我正在尝试确定是否有办法始终打印Thread: main

2 个答案:

答案 0 :(得分:2)

不。当import types import copyreg import multiprocessing def my_reduce(obj): return (obj.__func__.__get__, (obj.__self__,)) copyreg.pickle(types.MethodType, my_reduce) multiprocessing.reduction.register(types.MethodType, my_reduce) 线程通过main被阻塞时,该线程专门在等待流的.block()onNextonComplete信号(毕竟,上游操作员已执行)。在调用上游操作符以执行操作符之前,它以某种方式重新获得控制权。

您可以做的最接近的事情是确保:

  1. 在特定的onError上执行订阅(通过Scheduler)并且
  2. 将来的完成价值发布在同一.subscribeOn上(通过Scheduler)。

例如:

.publishOn

但是,请注意以下几点:

  • 订阅发生在Scheduler scheduler = Schedulers.parallel(); var res = Mono.fromFuture(future) .doFirst(() -> { // Note: doFirst added in 3.2.10.RELEASE // prints a thread in the parallel Scheduler (specified by subscribeOn below) System.out.println("Subscribe Thread: " + Thread.currentThread().getName()); }) // specifies the Scheduler on which the the completion value // from above is published for downstream operators .publishOn(scheduler) .map(val -> { // prints a thread in the parallel Scheduler (specified by publishOn above) System.out.println("Operator Thread: " + Thread.currentThread().getName()); return val + "1"; }) // specifies the Scheduler on which upstream operators are subscribed .subscribeOn(scheduler); 中的线程上,不是被阻塞的Scheduler线程中。
  • 此方法仅确保使用相同的main,而不是Scheduler中使用相同的Thread。从理论上讲,您可以使用单线程调度程序(例如Scheduler)强制使用相同的Thread
  • Schedulers.newParallel("single-threaded", 1)不会强制 所有运算符在该.publishOn上进行操作。它只会影响下游运算符,直到下一个Scheduler为止,或者直到下一个可能使用不同的.publishOn的异步运算符(例如.flatMap)为止。

答案 1 :(得分:0)

作为一种非常未经优化的概念证明,可以通过以下方式实现:

让我们创建一个执行器,该执行器能够以受控方式“按需”执行任务。

private static class SelfEventLoopExecutor implements Executor {
  private final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();

  @Override
  public void execute(Runnable command) {
    boolean added = queue.add(command);
    assert added;
  }

  public void drainQueue() {
    Runnable r;
    while ((r = queue.poll()) != null) {
      r.run();
    }
  }
}

接下来,创建一个订阅者,该订阅者可以使用执行程序在等待结果的同时执行任务,而不是完全阻塞线程。

public static class LazyBlockingSubscriber<T> implements Subscriber<T> {
  private final SelfEventLoopExecutor selfExec;
  private volatile boolean completed = false;
  private volatile T value;
  private volatile Throwable ex;

  public LazyBlockingSubscriber(SelfEventLoopExecutor selfExec) {
    this.selfExec = selfExec;
  }

  @Override
  public void onSubscribe(Subscription s) {
    s.request(1);
  }

  @Override
  public void onNext(T t) {
    value = t;
    completed = true;
  }

  @Override
  public void onError(Throwable t) {
    ex = t;
    completed = true;
  }

  @Override
  public void onComplete() {
    completed = true;
  }

  public T block() throws Throwable {
    while (!completed) {
      selfExec.drainQueue();
    }
    if (ex != null) {
      throw ex;
    }
    return value;
  }
}

现在,我们可以通过以下方式修改代码

public static void main(String[] args) throws Throwable {
  var future = new CompletableFuture<String>();

  var selfExec = new SelfEventLoopExecutor(); // our new executor

  var res = Mono.fromFuture(future)
      .publishOn(Schedulers.fromExecutor(selfExec))  // schedule on the new executor
      .map(val -> {
        System.out.println("Thread: " + Thread.currentThread().getName());
        return val + "1";
      });

  new Thread(() -> {
    try {
      Thread.sleep(1000L);
    } catch (InterruptedException e) {
    }
    future.complete("completed");
  }, "completer").start();

  var subs = new LazyBlockingSubscriber<String>(selfExec); // lazy subscribe
  res.subscribeWith(subs);
  subs.block(); // spin wait
}

结果,代码将打印Thread: main