java中Future的应用

Future接口是Java标准API的一部分,在java.util.concurrent包中。Future接口是Java线程Future模式的实现,可以来进行异步计算。
有了Future就可以进行三段式的编程了,1.启动多线程任务2.处理其他事3.收集多线程任务结果。从而实现了非阻塞的任务调用。在途中遇到一个问题,那就是虽然能异步获取结果,但是Future的结果需要通过isdone来判断是否有结果,或者使用get()函数来阻塞式获取执行结果。这样就不能实时跟踪其他线程的结果状态了,所以直接使用get还是要慎用,最好配合isdone来使用。

Future是java为了实现代码异步调用,Future的实现类有java.util.concurrent.FutureTask<V>javax.swing.SwingWorker<T,V>。通常使用FutureTask来处理我们的任务。FutureTask类同时又实现了Runnable接口,所以可以直接提交给Executor执行。

Future接口提供方法来检测任务是否被执行完,等待任务执行完获得结果,也可以设置任务执行的超时时间。这个设置超时的方法就是实现Java程序执行超时的关键。future.get(500,TimleMules);设置超时时间。

Future接口是一个泛型接口,严格的格式应该是Future<V>,其中V代表了Future执行的任务返回值的类型。在future异步执行期间程序可以去做其他的事情,future是异步进行的,如果有多个任务同时执行,则执行的总时间是其中返回的最迟的哪一个时间,即为所有任务中费时最少的哪一个,提高了时间。例如要同时执行时间如果用同步的话需要单个任务的10倍,但是异步只需要单个的时间就可以完成,统一放在future集合中供调用者调用。 Future接口的方法介绍如下:

  1. boolean cancel (boolean mayInterruptIfRunning) 取消任务的执行。参数指定是否立即中断任务执行,或者等等任务结束

2.      boolean isCancelled () 任务是否已经取消,任务正常完成前将其取消,则返回 true

3.    boolean isDone() 任务是否已经完成。需要注意的是如果任务正常终止、异常或取消,都将返回true

4.    V get () throwsInterruptedException, ExecutionException  等待任务执行结束,然后获得V类型的结果。InterruptedException 线程被中断异常,ExecutionException任务执行异常,如果任务被取消,还会抛出CancellationException

5.    V get (longtimeout, TimeUnit unit) throws InterruptedException, ExecutionException,TimeoutException 同上面的get功能一样,多了设置超时时间。参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。如果计算超时,将抛出TimeoutException

 

使用FutureTask实现超时执行的代码如下:

ExecutorServiceexecutor = Executors.newSingleThreadExecutor();

FutureTask<String>future =

    new FutureTask<String>(new Callable<String>() {//使用Callable接口作为构造参数

     public String call() {

      //真正的任务在这里执行,这里的返回值类型为String,可以为任意类型

    }});

executor.execute(future);

//在这里可以做别的任何事情

try {

  result =future.get(5000, TimeUnit.MILLISECONDS); //取得结果,同时设置超时执行时间为5秒。同样可以用future.get(),不设置执行超时时间取得结果

} catch (InterruptedException e) {

  futureTask.cancel(true); }

使用ExecutorService.submit方法来获得Future对象,submit方法即支持以 Callable接口类型,也支持Runnable接口作为参数,具有很大的灵活性。使用示例如下:

ExecutorServiceexecutor = Executors.newSingleThreadExecutor(); 

FutureTask<String>future = executor.submit( 

  new Callable<String>() {//使用Callable接口作为构造参数 

    public String call() { 

   //真正的任务在这里执行,这里的返回值类型为String,可以为任意类型 

  }}); 

Callable接口和Runnable接口的比:

Callable有返回值的接口,Runnable普通的不带返回值的接口,用来创建线程

为了使用异步之后返回调用结果可以在execute之后使用FutureCallbac进行回调

回调的一些函数还有listening等一些。

final CountDownLatch latch = new CountDownLatch(1);

final HttpGet request = new
HttpGet("https://www.alipay.com/");
System.out.println("
caller thread id is : " + Thread.currentThread().getId());

httpclient.execute(request,
new FutureCallback<HttpResponse>() {

 

 

public void
completed(final HttpResponse response) { latch.countDown();
System.out.println("
callback thread id is : " + Thread.currentThread().getId());
System.out.println(request.getRequestLine() + "->" + response.getStatusLine());
try { String content =
EntityUtils.toString(response.getEntity(), "UTF-8"); System.out.println(" response content is : " + content); }
catch (IOException e) { e.printStackTrace(); } } public void failed(final Exception ex) { latch.countDown();
System.out.println(request.getRequestLine() + "->" + ex); System.out.println(" callback thread id is : " + Thread.currentThread().getId()); }
public void cancelled() { latch.countDown();
System.out.println(request.getRequestLine() + " cancelled"); System.out.println(" callback thread id is : " + Thread.currentThread().getId()); }

 

 

下面时一个在工作中的应用

AsyncHttpClient client=new DefaultAsyncHttpClient();

BoundRequestBuilder builder = httpClient.preparePost(dispatcherURL);

builder.addHeader("Content-Type", "application/json;charset=UTF-8");

 builder.setBody(params.toJSONString());

Future<SkillResult> 
futu
re=builder.execute(request, newAsyncHandler)

protected static AsyncHandler<SkillResult> newAsyncHandler(String skillId) {

  return new AsyncHandler<SkillResult>() {

private ByteArrayOutputStream buffer = new ByteArrayOutputStream();

 @Override

 public State onStatusReceived(HttpResponseStatus responseStatus) throws Exception {

return State.CONTINUE

 }

@Override

 public State onHeadersReceived(HttpHeaders headers) throws Exception {

 return State.CONTINUE;

 }

@Override

 public State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception {

 buffer.write(bodyPart.getBodyPartBytes());

 return State.CONTINUE;

}

@Override

 public void onThrowable(Throwable t) {

t.printStackTrace()

 } 

@Override

public SkillResult onCompleted() throws Exception {

SkillResult result = new SkillResult();result.skillId = skillId;

result.content = buffer.toString();

return result

}

}

用Java8的CompletableFuture类,下面是异步调用dispatch方法然后返回Fulture;

Fulture<CenterResponse> fulture =CompletableFuture.<CenterResponse>supplyAsync(() -> dispatch(request));

 

@Configuration
public class WebSocketConfiguration {
   /**
     * 工作线程池
     * @return
     */
    @Bean(destroyMethod = "shutdown")
    public ExecutorService executorService() {
        return new TraceExecutorService(new ThreadPoolExecutor(config.getCorePoolSize(),
                config.getMaxPoolSize(),
                config.getIdleTime(),
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<Runnable>(config.getBlockingDequeSize()),
                new ThreadFactory() {
                    int threadId = 1;
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread t = new Thread(new MDCContinueRunableDecorator(r));
                        t.setName("worker thread " + (threadId++));
                        t.setDaemon(true);

                        if (threadId > 100000)
                            threadId = 1;
                        return t;
                    }
                }));
    }

}
   @AutoWired
  protected ExecutorService threadPool;
  
  List<CompletableFuture<List<SkillResult>>> futures = new ArrayList<>();
            for (int id : skillIds) {
                SkillAutomationModel skillModel = dfaModel.getSkillModels().get(id);
                if(skillModel != null) {
                    futures.add(CompletableFuture.supplyAsync(()->runFirstSkill(graph, checker, skillModel), threadPool));
                }
            }
            return futures.stream().map(CompletableFuture::join).flatMap(item->item.stream()).collect(Collectors.toList());

在项目中使用时首先使用@Bean(destroyMethod = "shutdown")定义一个线程池对象,

然后在使用的地方用@Autowired注入即可在

futures.add(CompletableFuture.supplyAsync(()->runFirstSkill(graph, checker, skillModel), threadPool));

在CompletableFuture.supplyAsync中如果只传入一个方法名则会系统级公共线程池找一个线程执行,如果后面添加了第二个参数threadPool则会在指定的线程池threadPool中取出一个线程执行。

supplyAsync用于有返回值的任务,runAsync则用于没有返回值的任务。Executor参数可以手动指定线程池,否则默认ForkJoinPool.commonPool()系统级公共线程池, 

只有当每个操作很复杂需要花费相对很长的时间(比如,调用多个其它的系统的接口;比如,商品详情页面这种需要从多个系统中查数据显示的)的时候用CompletableFuture才合适,不然区别真的不大,还不如顺序同步执行。

CompletableFuture是JDK1.8才新加入的一个实现类,实现了Future<T>CompletionStage<T>两个接口。

thenApply相当于回调函数(callback)

runAsync(Runnable runnable) 使用ForkJoinPool.commonPool()作为它的线程池执行异步代码。

runAsync(Runnable runnable, Executor executor) 使用指定的thread pool执行异步代码。

supplyAsync(Suppliersupplier) 使用ForkJoinPool.commonPool()作为它的线程池执行异步代码,异步操作有返回值 supplyAsync(Supplier supplier, Executor executor) 使用指定的thread pool执行异步代码,异步操作有返回值 runAsync 和 supplyAsync 方法的区别是runAsync返回的CompletableFuture是没有返回值的。

allOf是等待所有任务完成,构造后CompletableFuture完成

anyOf是只要有一个任务完成,构造后CompletableFuture就完成

join等待执行完毕

List<String> list = new ArrayList<>();

        ExecutorService executorService = Executors.newFixedThreadPool(10);

        List<Integer> taskList = Arrays.asList(2, 1, 3, 4, 5, 6, 7, 8, 9, 10);
        // 全流式处理转换成CompletableFuture[]+组装成一个无返回值CompletableFuture,join等待执行完毕。返回结果whenComplete获取
        CompletableFuture[] cfs = taskList.stream()
                .map(integer -> CompletableFuture.supplyAsync(() -> calc(integer), executorService)
                                .thenApply(h->Integer.toString(h))
                                .whenComplete((s, e) -> {
                                    System.out.println("任务"+s+"完成!result="+s+",异常 e="+e+","+new Date());
                                    list.add(s);
                                })
                ).toArray(CompletableFuture[]::new);
        // 封装后无返回值,必须自己whenComplete()获取
        CompletableFuture.allOf(cfs).join();
        System.out.println("list="+list+",耗时="+(System.currentTimeMillis()-start));

直接将已知的结果转换为Fulter的方法如下。

CompletableFuture<String> cf = CompletableFuture.completedFuture("message");

cf.getNow(null)    函数getNow(null)返回计算结果或者 null。

假设我们本次计算只需要前一次的计算结果,而不需要返回本次计算结果,那就有点类似于生产者(前一次计算)-消费者(本次计算)模式了(同步模式)

StringBuilder result = new StringBuilder();

    CompletableFuture.completedFuture("thenAccept message")

    .thenAccept(s ->result.append(s));

(以下是异步模式,需要利用CompletableFuture.join),所有的异步后面都是多了Async。

 StringBuilder result = new StringBuilder();

    CompletableFuture<Void>cf = CompletableFuture.completedFuture("thenAcceptAsync message")

    .thenAcceptAsync(s ->result.append(s));

    cf.join();

CompletableFuture 是异步执行方式;使用 ForkJoinPool 实现异步执行,这种方式使用了 daemon 线程执行 Runnable 任务。

我们可以通过调用 cancel(boolean mayInterruptIfRunning)方法取消计算任务。此外,cancel()方法与 completeExceptionally(new CancellationException())等价

通过 thenCombine()方法整合两个异步计算的结果

f的whenComplete的内容由哪个线程来执行,取决于哪个线程X执行了f.complete()。但是当X线程执行了f.complete()的时候,whenComplete还没有被执行到的时候(就是事件还没有注册的时候),那么X线程就不会去同步执行whenComplete的回调了。这个时候哪个线程执行到了whenComplete的事件注册的时候,就由哪个线程自己来同步执行whenComplete的事件内容。

而whenCompleteAsync的场合,就简单很多。一句话就是线程池里面拿一个空的线程或者新启一个线程来执行回调。和执行f.complete的线程以及执行whenCompleteAsync的线程无关。

添加回调方法的流程是从 thenApply 开始的:

get()会堵塞当前的线程,这就造成了一个问题,如果执行线程迟迟没有返回数据,get()会一直等待下去,因此,第二个

V get(long timeout,Timeout unit);方法可以设置等待的时间.getNow()方法比较有意思,表示当有了返回结果时会返回结果,如果异步线程抛了异常会返回自己设置的默认值.

thenAccept()当前任务正常完成以后执行,当前任务的执行结果可以作为下一任务的输入参数,无返回值.

场景:执行任务A,同时异步执行任务B,待任务B正常返回之后,用B的返回值执行任务C,任务C无返回值。

CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "任务A");
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> "任务B");
CompletableFuture<String> futureC = futureB.thenApply(b -> {
      System.out.println("执行任务C.");
      System.out.println("参数:" + b);//参数:任务B
      return "a";
});

thenRun(..)功能:对不关心上一步的计算结果,执行下一个操作

场景:执行任务A,任务A执行完以后,执行任务B,任务B不接受任务A的返回值(不管A有没有返回值),也无返回值

CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "任务A");
futureA.thenRun(() -> System.out.println("执行任务B"));

thenApply(..)功能:当前任务正常完成以后执行,当前任务的执行的结果会作为下一任务的输入参数,有返回值

场景:多个任务串联执行,下一个任务的执行依赖上一个任务的结果,每个任务都有输入和输出

实例1:异步执行任务A,当任务A完成时使用A的返回结果resultA作为入参进行任务B的处理,可实现任意多个任务的串联执行

CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "hello");

CompletableFuture<String> futureB = futureA.thenApply(s->s + " world");

CompletableFuture<String> future3 = futureB.thenApply(String::toUpperCase);

System.out.println(future3.join());

上面的代码,我们当然可以先调用future.join()先得到任务A的返回值,然后再拿返回值做入参去执行任务B,而thenApply的存在就在于帮我简化了这一步,我们不必因为等待一个计算完成而一直阻塞着调用线程,而是告诉CompletableFuture你啥时候执行完就啥时候进行下一步. 就把多个任务串联起来了.。

thenCombine(..)  thenAcceptBoth(..)  runAfterBoth(..)功能:结合两个CompletionStage的结果,进行转化后返回

场景:需要根据商品id查询商品的当前价格,分两步,查询商品的原始价格和折扣,这两个查询相互独立,当都查出来的时候用原始价格乘折扣,算出当前价格. 使用方法:thenCombine(..)

CompletableFuture<Double> futurePrice = CompletableFuture.supplyAsync(() -> 100d);
 CompletableFuture<Double> futureDiscount = CompletableFuture.supplyAsync(() -> 0.8);
 CompletableFuture<Double> futureResult = futurePrice.thenCombine(futureDiscount, (price, discount) -> price * discount);
 System.out.println("最终价格为:" + futureResult.join()); //最终价格为:80.0

thenCombine(..)是结合两个任务的返回值进行转化后再返回,那如果不需要返回呢,那就需要thenAcceptBoth(..),同理,如果连两个任务的返回值也不关心呢,那就需要runAfterBoth了,如果理解了上面三个方法,thenApply,thenAccept,thenRun,这里就不需要单独再提这两个方法了,只在这里提一下.。

thenCompose(..)功能:这个方法接收的输入是当前的CompletableFuture的计算值,返回结果将是一个新的CompletableFuture

thenApply():它的功能相当于将CompletableFuture<T>转换成CompletableFuture<U>,改变的是同一个CompletableFuture中的泛型类型。thenCompose():用来连接两个CompletableFuture,返回值是一个新的CompletableFuture

 

CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "hello");

CompletableFuture<String> futureB = futureA.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + "world"));

CompletableFuture<String> future3 = futureB.thenCompose(s -> CompletableFuture.supplyAsync(s::toUpperCase));

System.out.println(future3.join());

applyToEither(..)  acceptEither(..)  runAfterEither(..)功能:执行两个CompletionStage的结果,那个先执行完了,就是用哪个的返回值进行下一步操作

场景:假设查询商品a,有两种方式,A和B,但是A和B的执行速度不一样,我们希望哪个先返回就用那个的返回值.

CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "通过方式A获取商品a";
        });
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "通过方式B获取商品a";
        });
CompletableFuture<String> futureC = futureA.applyToEither(futureB, product -> "结果:" + product);
System.out.println(futureC.join()); //结果:通过方式A获取商品a

同样的道理,applyToEither的兄弟方法还有acceptEither(),runAfterEither()

exceptionally(..)功能:当运行出现异常时,调用该方法可进行一些补偿操作,如设置默认值.相当于catch功能,在发生异常情况时执行的逻辑。

CompletableFuture<String> futureA = CompletableFuture.
                supplyAsync(() -> "执行结果:" + (100 / 0))
                .thenApply(s -> "futureA result:" + s)
                .exceptionally(e -> {
                    System.out.println(e.getMessage()); //java.lang.ArithmeticException: / by zero
                    return "futureA result: 100";
                });
CompletableFuture<String> futureB = CompletableFuture.
                supplyAsync(() -> "执行结果:" + 50)
                .thenApply(s -> "futureB result:" + s)
                .exceptionally(e -> "futureB result: 100");
System.out.println(futureA.join());//futureA result: 100
System.out.println(futureB.join());//futureB result:执行结果:50

whenComplete(..)功能:当CompletableFuture的计算结果完成,或者抛出异常的时候,都可以进入whenComplete方法执行,举个栗子

CompletableFuture<String> futureA = CompletableFuture.
                supplyAsync(() -> "执行结果:" + (100 / 0))
                .thenApply(s -> "apply result:" + s)
                .whenComplete((s, e) -> {
                    if (s != null) {
                        System.out.println(s);//未执行
                    }
                    if (e == null) {
                        System.out.println(s);//未执行
                    } else {
                        System.out.println(e.getMessage());//java.lang.ArithmeticException: / by zero
                    }
                })
                .exceptionally(e -> {
                    System.out.println("ex"+e.getMessage()); //ex:java.lang.ArithmeticException: / by zero
             return "futureA result: 100"; }); 
System.out.println(futureA.join());//futureA result: 100

根据控制台,我们可以看出执行流程是这样,supplyAsync->whenComplete->exceptionally,可以看出并没有进入thenApply执行,原因也显而易见,在supplyAsync中出现了异常,thenApply只有当正常返回时才会去执行.而whenComplete不管是否正常执行,还要注意一点,whenComplete是没有返回值的.

CompletableFuture<String> futureA = CompletableFuture.
                supplyAsync(() -> "执行结果:" + (100 / 0))
                .thenApply(s -> "apply result:" + s)
                .exceptionally(e -> {
                    System.out.println("ex:"+e.getMessage()); //ex:java.lang.ArithmeticException: / by zero
                    return "futureA result: 100";
                })
                .whenComplete((s, e) -> {
                    if (e == null) {
                        System.out.println(s);//futureA result: 100
                    } else {
                        System.out.println(e.getMessage());//未执行
                    }
                })
                ;
System.out.println(futureA.join());//futureA result: 100

代码先执行了exceptionally后执行whenComplete,可以发现,由于在exceptionally中对异常进行了处理,并返回了默认值,whenComplete中接收到的结果是一个正常的结果,被exceptionally美化过的结果。

handle(..)功能:当CompletableFuture的计算结果完成,或者抛出异常的时候,可以通过handle方法对结果进行处理

CompletableFuture<String> futureA = CompletableFuture.
                supplyAsync(() -> "执行结果:" + (100 / 0))
                .thenApply(s -> "apply result:" + s)
                .exceptionally(e -> {
                    System.out.println("ex:" + e.getMessage()); //java.lang.ArithmeticException: / by zero
                    return "futureA result: 100";
                })
                .handle((s, e) -> {
                    if (e == null) {
                        System.out.println(s);//futureA result: 100
                    } else {
                        System.out.println(e.getMessage());//未执行
                    }
                    return "handle result:" + (s == null ? "500" : s);
                });
System.out.println(futureA.join());//handle result:futureA result: 100

通过控制台,我们可以看出,最后打印的是handle result:futureA result: 100,执行exceptionally后对异常进行了"美化",返回了默认值,那么handle得到的就是一个正常的返回,我们再试下,先调用handle再调用exceptionally的情况.

CompletableFuture<String> futureA = CompletableFuture.
                supplyAsync(() -> "执行结果:" + (100 / 0))
                .thenApply(s -> "apply result:" + s)
                .handle((s, e) -> {
                    if (e == null) {
                        System.out.println(s);//未执行
                    } else {
                        System.out.println(e.getMessage());//java.lang.ArithmeticException: / by zero
                    }
                    return "handle result:" + (s == null ? "500" : s);
                })
                .exceptionally(e -> {
                    System.out.println("ex:" + e.getMessage()); //未执行
                    return "futureA result: 100";
                });
System.out.println(futureA.join());//handle result:500

根据控制台输出,可以看到先执行handle,打印了异常信息,并对接过设置了默认值500,exceptionally并没有执行,因为它得到的是handle返回给它的值,由此我们大概推测handle和whenComplete的区别

   1.都是对结果进行处理,handle有返回值,whenComplete没有返回值

   2.由于1的存在,使得handle多了一个特性,可在handle里实现exceptionally的功能

allOf(..)  anyOf(..)

allOf:当所有的CompletableFuture都执行完后执行计算

anyOf:最快的那个CompletableFuture执行完之后执行计算

场景二:查询一个商品详情,需要分别去查商品信息,卖家信息,库存信息,订单信息等,这些查询相互独立,在不同的服务上,假设每个查询都需要一到两秒钟,要求总体查询时间小于2秒.

public static void main(String[] args) throws Exception {

        ExecutorService executorService = Executors.newFixedThreadPool(4);

        long start = System.currentTimeMillis();
        CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000 + RandomUtils.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "商品详情";
        },executorService);

        CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000 + RandomUtils.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "卖家信息";
        },executorService);

        CompletableFuture<String> futureC = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000 + RandomUtils.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "库存信息";
        },executorService);

        CompletableFuture<String> futureD = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000 + RandomUtils.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "订单信息";
        },executorService);

        CompletableFuture<Void> allFuture = CompletableFuture.allOf(futureA, futureB, futureC, futureD);
        allFuture.join();

        System.out.println(futureA.join() + futureB.join() + futureC.join() + futureD.join());
        System.out.println("总耗时:" + (System.currentTimeMillis() - start));

异步计算

所谓异步调用其实就是实现一个可无需等待被调用函数的返回值而让操作继续运行的方法。在 Java 语言中,简单的讲就是另启一个线程来完成调用中的部分计算,使调用继续运行或返回,而不需要等待计算结果。但调用者仍需要取线程的计算结果。

回调函数

回调函数比较通用的解释是,它是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。

回调函数的机制:

(1)定义一个回调函数;

(2)提供函数实现的一方在初始化时候,将回调函数的函数指针注册给调用者;

(3)当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

回调函数通常与原始调用者处于同一层次,

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

   

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值