1. Asinxron əməliyyatlarda səhvlərin işlənməsi
Sinxron kodda hər şey sadədir: fayl tapılmadıqda və ya icazə olmadıqda, istisnanı dərhal try-catch içində tutursunuz. Asinxron kodda isə, xüsusən də kolbeyklərdən (CompletionHandler) istifadə edəndə, xəta metodunuz bitdikdən sonra — axın hovuzunun dərinliklərində — baş verə bilər. Onu düzgün idarə etməsəniz, proqram qeyri-proqnozlaşdırılan şəkildə davrana bilər: “sakit” məlumat itkisinə qədər və ya bütün tətbiqin çökməsinə qədər.
Səhvlər CompletionHandler-ə necə ötürülür?
CompletionHandler<V, A> interfeysində iki metod var:
- completed(V result, A attachment) — əməliyyat uğurla başa çatdıqda çağırılır.
- failed(Throwable exc, A attachment) — xəta baş verdikdə çağırılır.
İstifadə nümunəsi:
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.*;
import java.util.concurrent.Future;
import java.io.IOException;
public class AsyncErrorDemo {
public static void main(String[] args) throws Exception {
Path path = Paths.get("nonexistent.txt");
ByteBuffer buffer = ByteBuffer.allocate(1024);
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
channel.read(buffer, 0, buffer, new java.nio.channels.CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("Uğurla oxundu " + result + " bayt");
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("Faylın oxunmasında xəta: " + exc.getMessage());
// Jurnallaşdırmaq, istifadəçini xəbərdar etmək, xətanı yuxarıya ötürmək olar
}
});
} catch (IOException ex) {
System.out.println("Faylın açılmasında xəta: " + ex.getMessage());
}
// Asinxron əməliyyatın başa çatması üçün vaxt verək (real tətbiqlərdə CountDownLatch və ya digər mexanizmlərdən istifadə edin)
Thread.sleep(500);
}
}
Burada nə baş verir?
- Fayl mövcud deyilsə, failed metodu uyğun istisna ilə (NoSuchFileException) çağırılacaq.
- Əməliyyat uğurla başa çatıbsa — completed işləyəcək.
Tipik xətaların nümunələri
- Fayl tapılmadı: NoSuchFileException
- Giriş icazəsi yoxdur: AccessDeniedException
- Oxu/yazma xətası: IOException-un müxtəlif alt sinifləri
- Buffer problemləri: BufferOverflowException, BufferUnderflowException
Jurnallaşdırma və istifadəçini məlumatlandırma
Asinxron kolbeykdə xəta — panikaya səbəb deyil, amma elə də etmək olmaz ki, guya heç nə baş verməyib. Yaxşı təcrübə — xətanı jurnallaşdırmaq (məsələn, Logger vasitəsilə), əgər bu, istifadəçi üçün vacibdirsə — mesaj göstərmək və ya UI-də emalçını çağırmaq.
Jurnallaşdırma nümunəsi:
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.err.println("Asinxron əməliyyat xətası: " + exc);
exc.printStackTrace();
}
İstehsal kodunda normal logger-lərdən (məsələn, java.util.logging və ya Log4j) istifadə edin, System.err deyil.
2. Asinxron əməliyyatların ləğvi
Əməliyyatın ləğvi nə zaman lazım ola bilər?
Bəzən asinxron tapşırığı işin ortasında dayandırmaq lazım olur. Məsələn, istifadəçi fikrini dəyişir və fayl yüklənməsi zamanı “Ləğv et” düyməsini sıxır. Yaxud proqram pəncərəsi bağlanır və əməliyyatın mənası qalmır. Elə hallar da olur ki, tətbiq yekunlaşarkən sadəcə resursları səliqəli şəkildə azad etmək gərəkir.
Belə hallarda Java-da asinxron giriş-çıxış ləğvi Future interfeysi vasitəsilə dəstəklənir. Onun köməyi ilə istənilən anda icrada olan tapşırığı dayandırmaq və resursları boş yerə sərf etməmək mümkündür.
Future vasitəsilə əməliyyatı necə ləğv etmək olar?
read və ya write metodu AsynchronousFileChannel daxilində Future<Integer> obyekti qaytarır. Bu obyektin cancel(boolean mayInterruptIfRunning) metodu var.
Nümunə: asinxron oxumanın ləğvi
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.*;
import java.util.concurrent.Future;
import java.io.IOException;
public class AsyncCancelDemo {
public static void main(String[] args) throws Exception {
Path path = Paths.get("bigfile.txt");
ByteBuffer buffer = ByteBuffer.allocate(1024);
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
Future<Integer> future = channel.read(buffer, 0);
// Bir az gözləyək, sonra əməliyyatı ləğv edək
Thread.sleep(100);
boolean cancelled = future.cancel(true);
if (cancelled) {
System.out.println("Oxuma əməliyyatı ləğv edildi!");
} else {
System.out.println("Əməliyyatı ləğv etmək mümkün olmadı (yəqin ki, o artıq tamamlanıb)");
}
}
}
}
Vacib incəliklər:
- Ləğv yalnız hələ tamamlanmamış əməliyyatlar üçün işləyir.
- Əməliyyat artıq bitibsə — onu ləğv etmək mümkün deyil.
- Ləğvdən sonra, bu Future üzərində get() çağırmağa cəhd etsəniz, CancellationException istisnası atılacaq.
Əməliyyatı nə vaxt artıq ləğv etmək mümkün deyil?
Tapşırıq artıq başa çatıbsa — istər uğurla, istər xətayla — onu dayandırmaq mümkün deyil, qatar gedib.
Bundan əlavə, bütün reallaşdırmalar əməliyyatları OS səviyyəsində həqiqətən dayandıra bilmir. Məsələn, bəzi fayl sistemləri ilə işləyərkən “ləğv” sırf simvolik ola bilər: əməliyyat icra olunmağa davam edəcək, siz isə sadəcə nəticəni görməzlikdən gələcəksiniz.
3. Praktika: səhvlərin işlənməsi və ləğv
Nümunə 1: mövcud olmayan faylı oxuyarkən xətanın işlənməsi
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.*;
import java.io.IOException;
public class AsyncErrorExample {
public static void main(String[] args) throws Exception {
Path path = Paths.get("no_such_file.txt");
ByteBuffer buffer = ByteBuffer.allocate(1024);
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
channel.read(buffer, 0, buffer, new java.nio.channels.CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("Əməliyyat uğurla başa çatdı");
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("Faylı oxuyarkən xəta: " + exc.getClass().getSimpleName() + " - " + exc.getMessage());
}
});
} catch (IOException ex) {
System.out.println("Faylı açarkən xəta: " + ex.getMessage());
}
Thread.sleep(500);
}
}
Konsolda nə görəcəyik?
Faylı açarkən xəta: no_such_file.txt
və ya, xəta açılışda yox, məhz oxuma zamanı baş verərsə:
Faylı oxuyarkən xəta: NoSuchFileException - no_such_file.txt
Nümunə 2: uzunmüddətli əməliyyatın ləğvi və düzgün tamamlanma
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.*;
import java.util.concurrent.Future;
import java.io.IOException;
public class AsyncCancelExample {
public static void main(String[] args) throws Exception {
Path path = Paths.get("bigfile.txt");
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024 * 10); // 10 MB
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
Future<Integer> future = channel.read(buffer, 0);
// 50 ms sonra əməliyyatı ləğv edirik (eksperiment üçün)
Thread.sleep(50);
boolean cancelled = future.cancel(true);
if (cancelled) {
System.out.println("Oxuma əməliyyatı ləğv edildi!");
} else {
System.out.println("Əməliyyatı ləğv etmək mümkün olmadı (yəqin ki, o artıq tamamlanıb)");
}
try {
// Nəticəni almağa cəhd edək (CancellationException atacaq)
future.get();
} catch (java.util.concurrent.CancellationException ex) {
System.out.println("CancellationException tutuldu: əməliyyat həqiqətən ləğv edilib.");
}
}
}
}
4. Ən yaxşı təcrübələr: düzgün necə etmək
Səhvlər olduqda belə resursları azad edin
try-with-resources ilə kanalların avtomatik bağlanması üçün istifadə edin:
try (AsynchronousFileChannel channel = /* open channel */ null) {
// ...
}
Əgər CompletionHandler istifadə edirsinizsə, bütün əməliyyatlar başa çatdıqda kanalı bağlamağı unutmayın. Bu, ard-arda bir neçə asinxron əməliyyat etdikdə xüsusilə vacibdir.
UI/əsas axını bloklamayın
Asinxron əməliyyatların məqsədi əsas axını bloklamamaqdır. UI axınında future.get() çağırmayın — əks halda asinxronluğun mənası itir.
Bütün xətaları jurnallaşdırın
CompletionHandler daxilində həmişə failed metodunu reallaşdırın və bütün istisnaları jurnallaşdırın (və ya yuxarıya ötürün).
Proqramı bağlamadan əvvəl bütün əməliyyatların bitdiyini yoxlayın
Əməliyyat bitməmiş proqram yekunlaşsa, nəticə itə bilər. Konsol nümayişlərində bəzən Thread.sleep(500) etmək lazım gəlir, lakin real tətbiqlərdə CountDownLatch, CompletableFuture və ya digər sinxronizasiya mexanizmlərindən istifadə edin.
Ləğvi unutmayın
Əməliyyata artıq ehtiyac yoxdursa (məsələn, istifadəçi pəncərəni bağlayıb), onu Future.cancel vasitəsilə ləğv edin. Bu, resurslara qənaət edəcək və tətbiqin cavabverməsini sürətləndirəcək.
5. Async IO-da səhvlərin işlənməsi və ləğvdə tipik səhvlər
Səhv №1: metodun görməməzlikdən gəlinməsi failed içində CompletionHandler.
Xəta emalı reallaşdırılmasa, tətbiqiniz qeyri-proqnozlaşdırılan şəkildə davranacaq: xətalar “itəcək”, istifadəçi isə nə üçün heç nə baş vermədiyini anlamayacaq.
Səhv №2: Əməliyyatlar bitdikdən sonra kanal bağlanmayıb.
AsynchronousFileChannel-ı bağlamağı unutmusunuz — resurs sızmaları və ola bilsin, OS-də faylın bloklanması ilə üzləşəcəksiniz.
Səhv №3: Asinxron əməliyyatın nəticəsini əsas axında gözləmək.
future.get() UI axınında çağırıldı — interfeys “dondu”, və bütün asinxronluq mənasını itirdi.
Səhv №4: Artıq tamamlanmış əməliyyatı ləğv etməyə cəhd.
cancel() çox gec çağırıldı — əməliyyat artıq başa çatıb, ləğv işləməyəcək. Bu, kritik deyil, amma sazlama zamanı çaşdırıcı ola bilər.
Səhv №5: Ləğvin nəticəsini yoxlamamaq.
cancel() çağırıldı, amma qaytarılan dəyər yoxlanmadı və CancellationException get() çağırılarkən emal edilmədi — proqram çökə bilər və ya qəribə davrana bilər.
Səhv №6: Xəta və ya ləğv zamanı resursları azad etməmək.
Əgər xəta və ya ləğvdən sonra kanal bağlanmasa, sızmalar və ya fayl bloklanmaları yarana bilər.
GO TO FULL VERSION