1. Giriş
Əvvəlcə vacib sual: niyə asinxron metodlara sadəcə etibar edib hər şeyin həmişə düzgün işləyəcəyini gözləmək olmaz? Fayl əməliyyatları tez-tez istisna atır — faylı silmiş ola bilərlər, yaddaş bitmiş ola bilər, icazə çatmaya bilər və ya fayl bloklanmış ola bilər. Sinxron kodda bu problemləri adi try-catch blokunda tutardınız. Asinxron kodda da fəlsəfə eynidir, amma incə məqamlar var: xəta metodun çağırıldığı anda deyil, əməliyyatın əslində yerinə yetirildiyi vaxt baş verə bilər.
Xəta yaranma anı
Sinxron kodda StreamReader.Read() ilə oxuyarkən istisna dəqiq çağırış sətrində atılacaq — catch-də tutdunuz və yaxşıdır.
Asinxron kodda (await stream.ReadAsync()) xəta əməliyyat başladığı anda deyil, tapşırığın (Task) tamamlandığı və uğursuzluqla nəticələndiyi await zamanı düşəcək. Əgər await-ı unutmusunuzsa, xəta müəyyən müddət «görünməz» qala bilər.
2. Asinxron metodlarda istisnaları necə tutmaq
Gəlin dərhal tipik şablona baxaq:
try
{
using FileStream fs = new FileStream("myfile.txt", FileMode.Open);
byte[] buffer = new byte[1024];
int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);
// Daha sonra emal...
}
catch (IOException ex)
{
Console.WriteLine("Giriş-çıxış xətası: " + ex.Message);
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("Fayla giriş yoxdur: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Naməlum xəta: " + ex);
}
Bəli-bəli, hər şey sadədir — tanış try-catch-dən istifadə edirik, amma asinxron metodda. Vacibdir ki, metod özü async açar sözü ilə olsun, yoxsa compiler hirslənəcək.
Vacib detal: await-ı harada qoymaq
Task<int> readTask = fs.ReadAsync(buffer, 0, buffer.Length);
// ... burada təsadüfən await və ya emal unutdunuz
Belə halda xəta baş verərsə, o, tapşırığın özünə (Task) düşəcək və siz onun haqqında nəticəni əldə etməyə çalışana qədər bilməyəcəksiniz — məsələn, await readTask ilə və ya Task.Exception vasitəsilə. Əgər ümumiyyətlə await-ı unutmusunuzsa — tapşırıq xəta ilə tamamlanacaq, sizə heç kim deməyəcək.
3. Niyə asinxron kod səhət emalı olmadan təhlükəlidir
Ssenari 1: «Fire and forget» — yeni başlayanların tələsi
FileStream fs = new FileStream("file.txt", FileMode.Open);
byte[] buffer = new byte[8000];
fs.ReadAsync(buffer, 0, buffer.Length);
// Sonra proqram öz həyatına davam edir
Oxuma əməliyyatı «paralel üzməyə» gedir və əgər o, xəta ilə bitərsə, heç bir catch onu tutmayacaq. İstisna tapşırığın içində gizlənəcək. Bu pattern-i «fire and forget» adlandırırlar və real tətbiqlərdə bu kritik səhətlərin itirilməsinə və resurs sızmasına səbəb ola bilər.
Ssenari 2: Asinxron metodlar await olmadan
Task t = MyAsyncMethod();
// ... burada nəsə edirsən, sonra t-i unudursan
MyAsyncMethod-in içində baş verən xətalar siz açıq şəkildə tapşırığı gözləməyənə qədər yuxarı qaldırılmayacaq (await t və ya t.Wait()).
4. Asinxron tapşırıqlardan gələn xətaları düzgün tutmaq
Strategiya 1: Həmişə await-dən istifadə et
try
{
await SomeFileOperationAsync();
}
catch (Exception ex)
{
Console.WriteLine("Bir şey səhv getdi: " + ex.Message);
}
Beləliklə, istisna birbaşa gözləmə yerində atılacaq və itkin düşməyəcək.
Strategiya 2: .ContinueWith vasitəsilə emal
Bir səbəbdən await-dən istifadə etmirsinizsə, ContinueWith ilə xəta handler-i əlavə etmək olar:
var task = fs.ReadAsync(buffer, 0, buffer.Length);
task.ContinueWith(t =>
{
if (t.Exception != null)
Console.WriteLine("Asinxron oxuma zamanı xəta: " + t.Exception.InnerException);
}, TaskContinuationOptions.OnlyOnFaulted);
Açığı desəm? Müasir C# tətbiqlərində belə yanaşma nadir hallarda istifadə olunur — async/await kodu daha sadə və təmiz edir.
Fayllarla asinxron işləyərkən mümkün istisnalar
- IOException — disk nasazlığı, fayl tapılmadı, yol çox uzun, cihaz əlçatmazdır.
- UnauthorizedAccessException — kifayət qədər icazə yoxdur.
- ObjectDisposedException — stream əməliyyat tamamlanmamışdan əvvəl bağlanıb.
- OperationCanceledException — əməliyyat CancellationToken vasitəsilə ləğv edilib.
5. Nümunə: Xətaların işlənməsi ilə asinxron oxuma
Bu loqikanı tətbiqimizə əlavə edək:
using System;
using System.IO;
using System.Threading.Tasks;
namespace FileAsyncDemo
{
class Program
{
static async Task Main()
{
string path = "bigfile.txt";
byte[] buffer = new byte[4096];
try
{
using FileStream fs = new FileStream(path, FileMode.Open);
int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);
Console.WriteLine($"Oxundu {bytesRead} bayt {path}-dən");
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Fayl tapılmadı: " + ex.Message);
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("Fayla giriş yoxdur: " + ex.Message);
}
catch (IOException ex)
{
Console.WriteLine("Oxuma/yazma xətası: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Başqa xəta: " + ex.Message);
}
}
}
}
Vacib qeyd
Əgər Main-də await-dən istifadə etmirsinizsə, və yalnız Task result = SomeAsyncMethod(); edirsinizsə — xətalar «səssiz» qalacaq və daha sonra, nəticəni əldə etməyə çalışdığınızda üzə çıxacaq.
6. Ən çox rast gəlinən səhvlər və "dərslər" xətaların emalında
await-ı asinxron metoda qoymağı unudurlar — xətalar vaxtında üzə çıxmır, proqram qeyri-müəyyən davranır.
Asinxron çağırışları try-catch-ə sarmırlar — tətbiq ilk nasazlıqda çökür.
Yalnız ümumi Exception-i emal edirlər, UnauthorizedAccessException və ya OperationCanceledException kimi spesifik istisnaları nəzərə almırlar — nəticədə pis diaqnostika olur.
«Fire and forget» tapşırıqlardan istifadə edir, amma xəta loglamasını etmirlər — istisnalar Task-in içində itir.
Tapşırığın bitdiyini uğurlu sayırlar. Nəticəni açıqca gözləmək (await) və istisnaları emal etmək lazımdır.
GO TO FULL VERSION