对Akka Future的理解(一)

《AkkaJava-v2.2.5》对Future描述如下:

4.1 Futures
4.1.1 Introduction
In the Scala Standard Library, a Future is adata structure used to retrieve the result of some concurrent operation.
This result can be accessed synchronously (blocking) or asynchronously(non-blocking). To be able to use this
from Java, Akka provides a java friendly interface in
akka.dispatch.Futures.
4.1.2 Execution Contexts
In order to execute callbacks andoperations, Futures need something called an ExecutionContext, which
is very similar to a
java.util.concurrent.Executor. if you have an ActorSystemin scope, it will
use its default dispatcher as the
ExecutionContext, or you can use the factory methods provided by the
ExecutionContexts class to wrap Executors and ExecutorServices, or even create your own.

import akka.dispatch.*;
import scala.concurrent.ExecutionContext;
import scala.concurrent.Future;
import scala.concurrent.Await;
import scala.concurrent.Promise;
import akka.util.Timeout;
import scala.concurrent.ExecutionContext;
import scala.concurrent.ExecutionContext$;
ExecutionContext ec =
ExecutionContexts.fromExecutorService(yourExecutorServiceGoesHere);
//Use ec with your Futures
Future<String> f1 = Futures.successful("foo");
// Then you shut down theExecutorService at the end of your application.
yourExecutorServiceGoesHere.shutdown();

4.1.3 Use with Actors
There are generally two ways ofgetting a reply from an UntypedActor : the first is by a sent message
(
actorRef.tell(msg, sender) ), which only works if the original sender was an UntypedActor ) and
the second is through a
Future .
Using the
ActorRef ‘s ask method to send a message will return a Future . To wait for andretrieve the actual
result the simplest method is:
import akka.dispatch.*;
import scala.concurrent.ExecutionContext;
import scala.concurrent.Future;
import scala.concurrent.Await;
import scala.concurrent.Promise;
import akka.util.Timeout;
Timeout timeout = new Timeout(Duration.create(5, "seconds"));
Future<Object> future = Patterns.ask(actor, msg, timeout);
String result = (String) Await.result(future, timeout.duration());

This will cause the current thread toblock and wait for the UntypedActor to ‘complete’ the Future with it’s
reply. Blocking is discouraged though as it can cause performance problem. Theblocking operations are located
in
Await.result and Await.ready to make it easy to spot where blocking occurs. Alternatives toblocking
are discussed further within this documentation. Also note that the
Future returned by an UntypedActor is a
Future<Object> since an UntypedActor is dynamic. That is why the cast to String is used in the above
sample.
Warning: Await.result and Await.ready are providedfor exceptional situations where you must
block, a good rule of thumb is toonly use them if you know why you must block. For all other cases, use
asynchronous composition as described below.
To send the result of a
Future to an Actor , you can use the pipe construct:
akka.pattern.Patterns.pipe(future,system.dispatcher()).to(actor);
4.1.4 Use Directly
A common use case within Akka is tohave some computation performed concurrently without needing the extra
utility of an
UntypedActor . If you find yourself creating a pool of UntypedActor s forthe sole reason of
performing a calculation in parallel, there is an easier (and faster) way:
import scala.concurrent.duration.Duration;
import akka.japi.Function;
import java.util.concurrent.Callable;
import static akka.dispatch.Futures.future;
import static java.util.concurrent.TimeUnit.SECONDS;
Future<String> f = future(new Callable<String>() {
public String call() {
return "Hello" + "World";
}
}, system.dispatcher());
f.onSuccess(new PrintResult<String>(), system.dispatcher());

In the above code the block passed to future will be executed by the default Dispatcher , withthe return
value of the block used to complete the
Future (in this case, theresult would be the string: “HelloWorld”).
Unlike a
Future that is returned from an UntypedActor , this Future is properly typed, and we also avoid
the overhead of managing an
UntypedActor .
You can also create already completed Futures using the
Futures class, which can beeither successes:
Future<String> future = Futures.successful("Yay!");

Or failures:
Future<String> otherFuture = Futures.failed(
new IllegalArgumentException("Bang!"));

It is also possible to create anempty Promise , to be filled later, and obtain the corresponding Future :
Promise<String> promise = Futures.promise();
Future<String> theFuture = promise.future();
promise.success("hello");

For these examples PrintResult isdefined as follows:
public final static class PrintResult<T> extends OnSuccess<T> {
@Override public final void onSuccess(T t) {
System.out.println(t);
}
}

4.1.5 Functional Futures
Scala’s Future has several monadicmethods that are very similar to the ones used by Scala ‘s collections.
These allow you to create ‘pipelines’ or ‘streams’ that the result will travelthrough.
Future is a Monad
The first method for working with Future functionally is map . This method takesa Mapper which performs
some operation on the result of the
Future , and returning a new result. The return value of the map method is
another
Future that will contain the new result:
import scala.concurrent.duration.Duration;
import akka.japi.Function;
import java.util.concurrent.Callable;
import static akka.dispatch.Futures.future;
import static java.util.concurrent.TimeUnit.SECONDS;
final ExecutionContext ec = system.dispatcher();
Future<String> f1 = future(new Callable<String>() {
public String call() {
return "Hello" + "World";
}
}, ec);
Future<Integer> f2 = f1.map(new Mapper<String, Integer>() {
public Integer apply(String s) {
return s.length();
}
}, ec);
f2.onSuccess(new PrintResult<Integer>(), system.dispatcher());
In this example we are joining twostrings together within a Future . Instead of waiting for f1 to complete, we
apply our function that calculates the length of the string using the
map method. Now we havea second Future ,
f2, that will eventually contain an
Integer . When our original Future , f1, completes, it will also apply our
function and complete the second
Future with its result. When we finally get the result, it willcontain the
number 10. Our original
Future still contains the string “HelloWorld” and is unaffected by the map .
Something to note when using these methods: if the
Future is still beingprocessed when one of these methods
are called, it will be the completing thread that actually does the work. Ifthe
Future is already complete though,
it will be run in our current thread. For example:
final ExecutionContext ec = system.dispatcher();
Future<String> f1 = future(new Callable<String>() {
public String call() throws Exception {
Thread.sleep(100);
return "Hello" + "World";
}
}, ec);
Future<Integer> f2 = f1.map(new Mapper<String, Integer>() {
public Integer apply(String s) {
return s.length();
}
}, ec);
f2.onSuccess(new PrintResult<Integer>(), system.dispatcher());

The original Future will take at least0.1 second to execute now, which means it is still being processed at the
time we call
map . The function we provide gets stored within the Future and later executedautomatically by
the dispatcher when the result is ready.
If we do the opposite:
final ExecutionContext ec = system.dispatcher();
Future<String> f1 = future(new Callable<String>() {
public String call() {
return "Hello" + "World";
}
}, ec);
// Thread.sleep is only here to provea point
Thread.sleep(100); // Do not use this in your code
Future<Integer> f2 = f1.map(new Mapper<String,Integer>() {
public Integer apply(String s) {
return s.length();
}
}, ec);
f2.onSuccess(new PrintResult<Integer>(), system.dispatcher());

Our little string has been processedlong before our 0.1 second sleep has finished. Because of this, the dispatcher
has moved onto other messages that need processing and can no longer calculatethe length of the string for us,
instead it gets calculated in the current thread just as if we weren’t using a
Future .
Normally this works quite well as it means there is very little overhead torunning a quick function. If there is
a possibility of the function taking a non-trivial amount of time to process itmight be better to have this done
concurrently, and for that we use
flatMap :
final ExecutionContext ec = system.dispatcher();
Future<String> f1 = future(new Callable<String>() {
public String call() {
return "Hello" + "World";
}
}, ec);
Future<Integer> f2 = f1.flatMap(new Mapper<String, Future<Integer>>() {
public Future<Integer> apply(final String s) {
return future(new Callable<Integer>() {
public Integer call() {
return s.length();
}
}, ec);
}
}, ec);
f2.onSuccess(new PrintResult<Integer>(), system.dispatcher());

Now our second Future is executedconcurrently as well. This technique can also be used to combine the results
of several Futures into a single calculation, which will be better explained inthe following sections.
If you need to do conditional propagation, you can use
filter :
final ExecutionContext ec = system.dispatcher();
Future<Integer> future1 = Futures.successful(4);
Future<Integer> successfulFilter = future1.filter(Filter.filterOf(
new Function<Integer, Boolean>() {
public Boolean apply(Integer i) {
return i % 2 == 0;
}
}), ec);
Future<Integer> failedFilter = future1.filter(Filter.filterOf(
new Function<Integer, Boolean>() {
public Boolean apply(Integer i) {
return i % 2 != 0;
}
}), ec);
//When filter fails, the returnedFuture will be failed with a scala.MatchError

Composing Futures
It is very often desirable to be ableto combine different Futures with each other, below are some examples on how
that can be done in a non-blocking fashion.
import static akka.dispatch.Futures.sequence;
final ExecutionContext ec = system.dispatcher();
//Some source generating a sequence ofFuture<Integer>:s
Iterable<Future<Integer>>listOfFutureInts = source;
// now we have aFuture[Iterable[Integer]]
Future<Iterable<Integer>>futureListOfInts = sequence(listOfFutureInts, ec);
// Find the sum of the odd numbers
Future<Long> futureSum = futureListOfInts.map(
new Mapper<Iterable<Integer>, Long>() {
public Long apply(Iterable<Integer> ints) {
long sum = 0;
for (Integer i : ints)
sum += i;
return sum;
}
}, ec);
futureSum.onSuccess(new PrintResult<Long>(), system.dispatcher());

To better explain what happened inthe example, Future.sequence is taking the
Iterable<Future<Integer>> and turning it into a Future<Iterable<Integer>> . We can
then use
map to work with the Iterable<Integer> directly, and we aggregate the sum ofthe Iterable .
The
traverse method is similar to sequence , but it takes a sequence of A‘‘s and applies a
function from ‘‘A
to Future<B> andreturns a Future<Iterable<B>> , enabling parallel map over
the sequence, if you use
Futures.future to create the Future .
import static akka.dispatch.Futures.traverse;
final ExecutionContext ec = system.dispatcher();
//Just a sequence of Strings
Iterable<String> listStrings = Arrays.asList("a", "b", "c");
Future<Iterable<String>> futureResult = traverse(listStrings,
new Function<String, Future<String>>() {
public Future<String> apply(final String r) {
return future(new Callable<String>() {
public String call() {
return r.toUpperCase();
}
}, ec);
}
}, ec);
//Returns the sequence of strings asupper case
futureResult.onSuccess(new PrintResult<Iterable<String>>(),system.dispatcher());

It’s as simple as that!
Then there’s a method that’s called
fold that takes a start-value, a sequence of Future :s and a functionfrom
the type of the start-value, a timeout, and the type of the futures and returnssomething with the same type as the
start-value, and then applies the function to all elements in the sequence offutures, non-blockingly, the execution
will be started when the last of the Futures is completed.
import akka.japi.Function2;
import static akka.dispatch.Futures.fold;
final ExecutionContext ec = system.dispatcher();
//A sequence of Futures, in this caseStrings
Iterable<Future<String>>futures = source;
//Start value is the empty string
Future<String> resultFuture = fold("", futures,
new Function2<String, String, String>() {
public String apply(String r, String t) {
return r + t; //Just concatenate
}
}, ec);
resultFuture.onSuccess(new PrintResult<String>(), system.dispatcher());

That’s all it takes!
If the sequence passed to
fold is empty, it will return the start-value, in the case above, thatwill be empty String.
In some cases you don’t have a start-value and you’re able to use the value ofthe first completing
Future in the
sequence as the start-value, you can use
reduce , it works likethis:
import static akka.dispatch.Futures.reduce;
final ExecutionContext ec = system.dispatcher();
//A sequence of Futures, in this caseStrings
Iterable<Future<String>>futures = source;
Future<Object> resultFuture = reduce(futures,
new Function2<Object, String, Object>() {
public Object apply(Object r, String t) {
return r + t; //Just concatenate
}
}, ec);
resultFuture.onSuccess(new PrintResult<Object>(), system.dispatcher());

Same as with fold , the execution willbe started when the last of the Futures is completed, you can also parallelize
it by chunking your futures into sub-sequences and reduce them, and then reducethe reduced results again.
This is just a sample of what can be done.
4.1.6 Callbacks
Sometimes you just want to listen toa Future being completed, and react to that not by creating a new Future,
but by side-effecting. For this Scala supports
onComplete , onSuccess and onFailure , ofwhich the latter
two are specializations of the first.
final ExecutionContext ec = system.dispatcher();
future.onSuccess(new OnSuccess<String>() {
public void onSuccess(String result) {
if ("bar" == result) {
//Do something if it resulted in"bar"
} else {
//Do something if it was some otherString
}
}
}, ec);
final ExecutionContext ec = system.dispatcher();
future.onFailure(new OnFailure() {
public void onFailure(Throwable failure) {
if (failure instanceof IllegalStateException) {
//Do something if it was thisparticular failure
} else {
//Do something if it was some otherfailure
}
}
}, ec);
final ExecutionContext ec = system.dispatcher();
future.onComplete(new OnComplete<String>() {
public void onComplete(Throwable failure, String result) {
if (failure != null) {
//We got a failure, handle it here
} else {
// We got a result, do something withit
}
}
}, ec);

4.1.7 Ordering
Since callbacks are executed in anyorder and potentially in parallel, it can be tricky at the times when you need
sequential ordering of operations. But there’s a solution! And it’s name is
andThen , and it creates anew Future
with the specified callback, a Future that will have thesame result as the Future it’s called on, which allows
for ordering like in the following sample:
final ExecutionContext ec = system.dispatcher();
Future<String> future1 = Futures.successful("value").andThen(
new OnComplete<String>() {
public void onComplete(Throwable failure, String result) {
if (failure != null)
sendToIssueTracker(failure);
}
}, ec).andThen(new OnComplete<String>() {
public void onComplete(Throwable failure, String result) {
if (result != null)
sendToTheInternetz(result);
}
}, ec);

4.1.8 Auxiliary methods
Future fallbackTo combines 2 Futures into a new Future , and will hold thesuccessful value of the second
Future if the first Future fails.
Future<String> future1 = Futures.failed(new IllegalStateException("OHNOES1"));
Future<String> future2 = Futures.failed(new IllegalStateException("OHNOES2"));
Future<String> future3 = Futures.successful("bar");
// Will have "bar" in thiscase
Future<String> future4 = future1.fallbackTo(future2).fallbackTo(future3);
future4.onSuccess(new PrintResult<String>(), system.dispatcher());
You can also combine two Futures intoa new Future that will hold a tuple of the two Futures successful results,
using the zip operation.
final ExecutionContext ec = system.dispatcher();
Future<String> future1 = Futures.successful("foo");
Future<String> future2 = Futures.successful("bar");
Future<String> future3 = future1.zip(future2).map(
new Mapper<scala.Tuple2<String, String>, String>() {
public String apply(scala.Tuple2<String, String> zipped) {
return zipped._1() + "" + zipped._2();
}
}, ec);
future3.onSuccess(new PrintResult<String>(), system.dispatcher());

4.1.9 Exceptions
Since the result of a Future is createdconcurrently to the rest of the program, exceptions must be handleddifferently. It doesn’t matter if an UntypedActor or the dispatcher is completing the Future , if an Exception
is caught the Future will contain itinstead of a valid result. If a Future does contain an Exception , calling
Await.result will cause it to be thrown again so it can be handled properly.
It is also possible to handle an
Exception by returning a different result. This is done with the recover
method. For example:
final ExecutionContext ec = system.dispatcher();
Future<Integer> future = future(new Callable<Integer>() {
public Integer call() {
return 1 / 0;
}
}, ec).recover(new Recover<Integer>() {
public Integer recover(Throwable problem) throws Throwable {
if (problem instanceof ArithmeticException)
return 0;
else
throw problem;
}
}, ec);
future.onSuccess(new PrintResult<Integer>(), system.dispatcher());

In this example, if the actor repliedwith a akka.actor.Status.Failure containing the
ArithmeticException , our Future would have a result of 0. The recover method works very
similarly to the standard try/catch blocks, so multiple
Exception s can behandled in this manner, and if an
Exception is not handled this way it will behave as if we hadn’t used the recover method.
You can also use the
recoverWith method, which has the same relationship to recover as flatMap has to
map , and is use like this:
final ExecutionContext ec = system.dispatcher();
Future<Integer> future = future(new Callable<Integer>() {
public Integer call() {
return 1 / 0;
}
}, ec).recoverWith(new Recover<Future<Integer>>() {
public Future<Integer> recover(Throwable problem) throws Throwable {
if (problem instanceof ArithmeticException) {
return future(new Callable<Integer>() {
public Integer call() {
return 0;
}
}, ec);
} else
throw problem;
}
}, ec);
future.onSuccess(new PrintResult<Integer>(), system.dispatcher());

4.1.10 After
akka.pattern.Patterns.after makes it easy to complete a Future with a value orexception after a
timeout.
import static akka.pattern.Patterns.after;
import java.util.Arrays;
final ExecutionContext ec = system.dispatcher();
Future<String> failExc = Futures.failed(new IllegalStateException("OHNOES1"));
Future<String> delayed = Patterns.after(Duration.create(200, "millis"),
system.scheduler(), ec, failExc);
Future<String> future = future(new Callable<String>() {
public String call() throws InterruptedException {
Thread.sleep(1000);
return "foo";
}
}, ec);
Future<String> result = Futures.firstCompletedOf(
Arrays.<Future<String>>asList(future, delayed), ec);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值