CodeGym /Kurslar /JAVA 25 SELF /Asinxron tapşırıqlar: thenApply, thenAccept, thenRun

Asinxron tapşırıqlar: thenApply, thenAccept, thenRun

JAVA 25 SELF
Səviyyə, Dərs
Mövcuddur

1. Asinxron tapşırığın işə salınması: supplyAsync və runAsync

Asinxron tapşırığı işə salmağın ən yayğın yolu — CompletableFuture.supplyAsync-dan istifadə etməkdir. Bu metod nəticə qaytaran lambda və ya metodu qəbul edir. Məsələn, serverdən məlumat yüklənməsini imitasiya etmək istəyirik:

import java.util.concurrent.CompletableFuture;

public class Main {
    public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // Uzunmüddətli əməliyyatın imitasiyası (məsələn, fayl yüklənməsi)
            sleep(1000);
            return "Serverdən məlumatlar";
        });

        System.out.println("Tapşırıq işə salındı!");
        // ... tapşırıq icra olunarkən başqa işlər görə bilərsiniz
    }

    private static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException ignored) {}
    }
}

runAsync: nəticə lazım olmadıqda

Əgər tapşırığınız heç nə qaytarmırsa (məsələn, sadəcə loqa yazır, bildiriş göndərir), runAsync-dan istifadə edin:

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    sleep(500);
    System.out.println("Əməliyyat başa çatdı!");
});

runAsync həmişə CompletableFuture<Void> qaytarır, çünki nəticə nəzərdə tutulmur.

2. thenApply, thenAccept, thenRun: fərq nədir?

Asinxron tapşırıq başa çatdıqda, adətən nəticə ilə nəsə etmək istəyirsiniz. Bunun üçün «emalçılar» metodları nəzərdə tutulub:

  • thenApply — nəticəni transformasiya edir və yeni nəticə qaytarır.
  • thenAccept — nəticəni qəbul edir, heç nə qaytarmır (yan təsirlər üçün istifadə olunur).
  • thenRun — nəticəni qəbul etmir, heç nə qaytarmır (sadəcə tapşırıq bitdikdən sonra əməliyyat yerinə yetirir).

thenApply: nəticənin emalı və çevrilməsi

Əgər əvvəlki tapşırığın nəticəsini çevirmək lazımdırsa, thenApply istifadə edin. Məsələn, sətir yükləmisiniz və indi onun uzunluğunu bilmək istəyirsiniz:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Java");

CompletableFuture<Integer> lengthFuture = future.thenApply(s -> {
    System.out.println("Sətirin uzunluğu hesablanır...");
    return s.length();
});

// lengthFuture indi Integer saxlayır — "Java" sətrinin uzunluğu
lengthFuture.thenAccept(len -> System.out.println("Uzunluq: " + len));

Nələr baş verir:

  • future "Java" sətrini saxlayır.
  • thenApply sətri onun uzunluğuna (int) çevirir.
  • thenAccept nəticəni çıxarır.

thenAccept: nəticə ilə əməliyyat (heç nə qaytarmır)

Nəticə ilə sadəcə nəsə etmək (məsələn, onu ekrana çıxarmaq) lazımdırsa və heç nə qaytarmırsınızsa — thenAccept istifadə edin:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Salam, dünya!");

future.thenAccept(result -> {
    System.out.println("Nəticə: " + result);
});

thenAccept “istehlakçı” kimidir: o, nəticəni “yeyir” və onunla faydalı bir şey edir.

thenRun: nəticəsiz əməliyyat

Əgər tapşırıq bitəndən sonra sadəcə nəsə icra etmək istəyirsinizsə, lakin nəticəyə ehtiyac yoxdur, thenRun istifadə edin:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hazırdır!");

future.thenRun(() -> {
    System.out.println("Yükləmə başa çatdı!");
});

Qeyd: thenRun daxilində əvvəlki tapşırığın nəticəsini əldə edə bilməzsiniz — o, sadəcə nəzərə alınmır.

3. Çağırış zəncirləri: tapşırıqlardan pipeline qururuq

CompletableFuture-un ən böyük gücü — hesablamalar zəncirini qura bilməsidir. Hər bir metod (thenApply, thenAccept, thenRun) yeni bir CompletableFuture qaytarır və ona yenidən emalçı əlavə etmək olar.

Nümunə: çoxmərhələli emal

Tətbiqimizi təkmilləşdirək: məlumatları yükləyək, onları çevirək, nəticəni çıxaraq və hər şeyin bitdiyini loqa yazaq.

CompletableFuture.supplyAsync(() -> {
    System.out.println("Addım 1: Məlumatları yükləyirik...");
    sleep(500);
    return "Java";
})
.thenApply(data -> {
    System.out.println("Addım 2: Məlumatları çeviririk...");
    return data.toUpperCase();
})
.thenAccept(result -> {
    System.out.println("Addım 3: Nəticəni çıxarırıq: " + result);
})
.thenRun(() -> {
    System.out.println("Addım 4: Hər şey başa çatdı!");
});

Konsol çıxışı:

Addım 1: Məlumatları yükləyirik...
Addım 2: Məlumatları çeviririk...
Addım 3: Nəticəni çıxarırıq: JAVA
Addım 4: Hər şey başa çatdı!

Qeyd:
Hər növbəti addım yalnız əvvəlki addım başa çatdıqdan sonra başlayır. Bu, məlumat emalı üçün həqiqi “pipeline-lər” qurmağa imkan verir.

4. Asinxron variantlar: thenApplyAsync, thenAcceptAsync, thenRunAsync

Susmaya görə emalçılar (thenApply, thenAccept, thenRun) əvvəlki tapşırığın bitdiyi eyni Thread-də icra olunur. Bəzən bu əlverişli deyil — emal ağırdırsa, onu ayrı Thread-ə çıxarmaq daha yaxşıdır.

Bunun üçün asinxron versiyalar mövcuddur:

  • thenApplyAsync
  • thenAcceptAsync
  • thenRunAsync

Fərq nədədir?

  • Async olmadan: emalçı əvvəlki tapşırığın bitdiyi eyni Thread-də icra oluna bilər (məsələn, tapşırıq ForkJoinPool-da başa çatıbsa, emalçı da orada).
  • Async ilə: emalçı mütləq başqa bir Thread-də — ForkJoinPool-dan (və ya sizin Executor-unuzdan) — icra olunacaq.

Nümunə: adi və asinxron emalçını müqayisə edək

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    System.out.println("Yüklənir... [" + Thread.currentThread().getName() + "]");
    return "Hello";
});

future.thenApply(result -> {
    System.out.println("thenApply: [" + Thread.currentThread().getName() + "]");
    return result + " World";
});

future.thenApplyAsync(result -> {
    System.out.println("thenApplyAsync: [" + Thread.currentThread().getName() + "]");
    return result + " Async World";
});

Tipik çıxış:

Yüklənir... [ForkJoinPool.commonPool-worker-1]
thenApply: [ForkJoinPool.commonPool-worker-1]
thenApplyAsync: [ForkJoinPool.commonPool-worker-2]

Nəticə:
Asinxron emalçı başqa Thread-də icra olunur.

Async metodlardan nə zaman istifadə etməli?

  • Emal resurs tutumludursa (məsələn, mürəkkəb hesablamalar, şəbəkə işi).
  • Əvvəlki tapşırığın başa çatdığı Thread-i bloklamaq istəmirsinizsə.
  • Thread-lərə açıq şəkildə nəzarət etmək istəyirsinizsə (məsələn, ikinci arqument kimi öz Executor-unuzu vermək).

5. Faydalı nüanslar

Cədvəl: thenApply, thenAccept, thenRun metodlarının müqayisəsi

Metod Nəticədən istifadə edir? Dəyər qaytarır? Nə üçün istifadə olunur
thenApply
Bəli Bəli Nəticənin çevrilməsi
thenAccept
Bəli Xeyr Yan effektlər (çıxış, jurnallaşdırma)
thenRun
Xeyr Xeyr Tapşırıq başa çatdıqdan sonra sadə əməliyyat
thenApplyAsync
Bəli Bəli Eyni şey, amma başqa bir Thread-də
thenAcceptAsync
Bəli Xeyr Eyni şey, amma başqa bir Thread-də
thenRunAsync
Xeyr Xeyr Eyni şey, amma başqa bir Thread-də

Sual: uzun zəncirlər necə qurulur?

Metodları bir-birinin ardınca, sanki LEGO konstruktoru kimi çağırmaq olar:

CompletableFuture.supplyAsync(() -> "42")
    .thenApply(Integer::parseInt)
    .thenApply(x -> x * 2)
    .thenAccept(x -> System.out.println("Nəticə: " + x));

Çıxış:

Nəticə: 84

Hər növbəti addım əvvəlkinin nəticəsini alır, onu dəyişə və ya sadəcə istifadə edə bilər.

6. thenApply, thenAccept, thenRun ilə işləyərkən tipik səhvlər

Səhv №1: Qaytarılan dəyərlərin tiplərində çaşqınlıq.
thenApply zəncir boyunca ötürüləcək dəyər qaytarmalıdır. Əgər təsadüfən thenApply istifadə edib nəticə qaytarmasanız, növbəti əməliyyat null alacaq (və ya ümumiyyətlə kompilyasiya olunmayacaq). Yan effektlər üçün thenAccept və ya thenRun istifadə edin.

Səhv №2: Nəticədən thenRun daxilində istifadə etməyə cəhd.
thenRun daxilində əvvəlki tapşırığın nəticəsinə çıxış yoxdur. Nəticədən istifadə etmək istəyirsinizsə, thenApply və ya thenAccept seçin.

Səhv №3: Əsas Thread-in bloklanması.
Əgər əsas Thread-də get() və ya join() çağırırsınızsa, asinxronluğun bütün üstünlüklərini itirirsiniz: Thread tapşırığın bitməsini gözləyəcək, sanki köhnə yaxşı sinxron kodda olduğu kimi. Yaxşısı bloklamayan zəncirlər və callback-lərdən istifadə etməkdir.

Səhv №4: Səhvlərin emal olunmaması.
Əgər zəncirdə istisna baş verərsə və siz emalçı əlavə etməmisinizsə (exceptionally, handle, whenComplete), o “itəcək” və tapşırıq görünməyən səhvlə nəticələnə bilər. Həmişə zəncirlərdə səhvləri emal edin.

Səhv №5: Gözlənilmədən başqa Thread-də icra.
Asinxron metodlar (thenApplyAsync və s.) başqa Thread-də icra oluna bilər. Əgər çoxsahəli girişdən qorunmayan dəyişənlərə müraciət edirsinizsə, məlumat yarışları yarana bilər.

1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Oyun Dünyasının Qızıl Ehtiyatı: Uğurların Qeydiyyatı
Oyun Dünyasının Qızıl Ehtiyatı: Uğurların Qeydiyyatı
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Gizli Mesajın Deşifrə Edilməsi: Kəşfiyyat Zənciri
Gizli Mesajın Deşifrə Edilməsi: Kəşfiyyat Zənciri
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION