Ders 02 / 19
Eşzamansız Dart
Genişlik sonradan gelen bir bileşenin iki ölçümü. Ders gelecek, async ve await, akış, olay döngüsü ve yalıtılmış işlemci kavramlarını ağacın kurulduğu sıra üzerinden anlatır.
Yayın Güncelleme
Bu derste
- Gelecek ve akış tiplerini, async ve await sözdizimini bir boyutlanma modeli üzerinde okur
- Olay döngüsünde senkron kodun, mikro görevlerin ve görev kuyruğunun çalışma sırasını açıklar
- Genişliği henüz gelmemiş bir bileşenin değer gelmeden önceki ve sonraki boyutlanmasını ölçer
- Hatalı bir geleceğin try ve catch ile yakalanıp yedek değere dönüştürüldüğünü gösterir
İçindekiler
Önceki derste Banner’ın 400 isteği hazır bir sayıydı. Gerçek bir ekranda bir bileşenin genişliği çoğu zaman sonradan gelir: bir görselin boyutu okunur, bir dosyanın içeriği çözülür, bir yanıt beklenir. O ana kadar ağaç yine de kurulmak zorundadır. Bu dersin sorusu, değer gelmeden önce ve geldikten sonra aynı bileşenin genişliğini kimin belirlediğidir.
Dart bu duruma gelecek (Future) tipiyle cevap verir. Future<int> şimdi var olmayan, ileride bir int ya da bir hata olarak tamamlanacak bir değerdir. async ile işaretlenen bir işlev her zaman bir gelecek döndürür; await ise bir gelecek tamamlanana kadar o işlevin geri kalanını bekletir, işlevi çağıran taraf bu sırada akışına devam eder.
Ağaç ve Yardımcı Dosya
Ders boyunca ağaç tek bir yerden kurulur. buildBody işlevi genişlik bilinmiyorsa fill('Avatar'), biliniyorsa leaf('Avatar', width) kullanır. printAvatar ağacı 360 genişliğinde ölçer ve yalnız /Screen/Body/Avatar satırını yazar. Bu dosya çıktısızdır ve main içermez; aşağıdaki bloklar onu içe alır.
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];
class Limit {
final int min;
final int? max; // null: sinirsiz
const Limit(this.min, this.max);
bool get tight => max == min;
Limit loosen() => Limit(0, max);
Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
String bounds() => '$min..${max ?? 'sonsuz'}';
}
int _floor(int value) => value < 0 ? 0 : value;
class Part {
final String kind;
final String name;
final int? width; // null: alabildigi kadar genis
final List<Part> children;
final int factor;
final bool tightFlex;
const Part(this.kind, this.name, this.width, this.children,
{this.factor = 0, this.tightFlex = true});
}
Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
Part('flex', name, null, [child], factor: factor, tightFlex: tight);
class Sizing {
final String sizing;
final String path;
final int width;
final Limit limit;
const Sizing(this.sizing, this.path, this.width, this.limit);
}
Sizing classify(String path, int? desire, Limit limit) {
if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
if (desire == null) {
return limit.max == null
? Sizing('kirpilan', path, limit.min, limit)
: Sizing('esnetilen', path, limit.max!, limit);
}
if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
if (limit.max != null && desire > limit.max!) {
return Sizing('kirpilan', path, limit.max!, limit);
}
return Sizing('secilen', path, desire, limit);
}
int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
final here = '$path/${part.name}';
final below = <Sizing>[];
if (part.kind == 'fixed') {
final own = classify(here, part.width, limit);
layout(part.children.first, Limit(own.width, own.width), below, here);
log..add(own)..addAll(below);
return own.width;
}
int? desire;
switch (part.kind) {
case 'leaf':
desire = part.width;
case 'pad':
final space = 2 * part.width!;
desire = layout(part.children.first, limit.deflate(space), below, here) + space;
case 'center':
final inner = layout(part.children.first, limit.loosen(), below, here);
desire = limit.max == null ? inner : null;
case 'flex':
desire = layout(part.children.first, limit, below, here);
case 'column':
var widest = 0;
for (final child in part.children) {
final width = layout(child, limit.loosen(), below, here);
if (width > widest) widest = width;
}
desire = widest;
case 'row':
final logs = [for (final _ in part.children) <Sizing>[]];
final bounded = limit.max != null;
var used = 0;
var factors = 0;
for (var i = 0; i < part.children.length; i++) {
final child = part.children[i];
if (child.kind == 'flex' && bounded) {
factors += child.factor;
} else {
used += layout(child, const Limit(0, null), logs[i], here);
}
}
final left = bounded ? _floor(limit.max! - used) : 0;
var given = 0;
var seen = 0;
for (var i = 0; i < part.children.length; i++) {
final child = part.children[i];
if (child.kind != 'flex' || !bounded) continue;
seen += child.factor;
final share = (seen == factors ? left : left * seen ~/ factors) - given;
given += share;
final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
used += layout(child, room, logs[i], here);
}
for (final entries in logs) {
below.addAll(entries);
}
desire = bounded ? null : used;
}
final own = classify(here, desire, limit);
log..add(own)..addAll(below);
return own.width;
}
List<Sizing> measure(Part root, int screen) {
final log = <Sizing>[];
layout(root, Limit(screen, screen), log);
return log;
}
void printSizes(List<Sizing> log) {
for (final entry in log) {
print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
}
}
void countSizings(List<Sizing> log) {
for (final name in sizings) {
print('$name: ${log.where((entry) => entry.sizing == name).length}');
}
print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
// avatar_body.dart — avatar genisligine gore sutun agaci kurar ve avatari olcer
import 'sizing.dart';
Part buildBody(int? width) {
final avatar = width == null ? fill('Avatar') : leaf('Avatar', width);
return pad('Screen', 16, column('Body', [leaf('Name', 120), avatar]));
}
void printAvatar(int? width) {
final log = measure(buildBody(width), 360);
printSizes(log.where((entry) => entry.path == '/Screen/Body/Avatar').toList());
}
int? width parametresi ilk dersteki fikri taşır: null, genişliğin henüz bilinmediğini söyler. width == null ? fill('Avatar') : leaf('Avatar', width) bir koşul ifadesidir; width null değilse ikinci kolda int olarak kullanılabilir, çünkü derleyici null denetimini izler.
DB1. Modelde bekleme süresi yoktur, yalnız sıra vardır. loadWidth hemen tamamlanır; ölçülen şey, değerin geldiği anın öncesi ve sonrasındaki iki ağaçtır. Model çerçevenin kendisi değildir.
Gelecek ve await
Aşağıdaki blok bir gelecek kurar, çağıran tarafın akışına devam ettiğini gösterir ve değeri await ile alır. Her adım bir satır basar.
// main.dart
import 'sizing.dart';
import 'avatar_body.dart';
Future<int> loadWidth() async {
print('yukleme basladi');
return 200;
}
Future<void> main() async {
print('istek gonderildi');
final pending = loadWidth();
print('beklemeden devam');
final width = await pending;
print('deger geldi: $width');
printAvatar(width);
}
istek gonderildi yukleme basladi beklemeden devam deger geldi: 200 secilen: /Screen/Body/Avatar genislik=200 kisit=0..328
loadWidth çağrıldığında gövdesi ilk await’e ya da sona kadar hemen çalışır; bu yüzden yukleme basladi satırı beklemeden devam’dan önce gelir. loadWidth’ın döndürdüğü gelecek pending değişkenine girer. await pending ifadesine gelindiğinde main durur ve değer gelince kaldığı yerden sürer. Değer gelmiştir, Avatar 200 ister ve aralığın içinde kaldığı için seçilendir.
Olay Döngüsü
Dart kodu tek bir iş parçacığında, bir olay döngüsü ile çalışır. Çalışan kod sonuna kadar sürer. Sonra döngü iki kuyruğa bakar: önce mikro görev kuyruğunu boşaltır, ardından görev kuyruğundan bir görev alır. Bir await ya da bir Future(...) çağrısı, devamını bu kuyruklardan birine bırakır. Aşağıdaki blok sırayı numaralarla gösterir.
// main.dart
import 'sizing.dart';
import 'dart:async';
import 'avatar_body.dart';
void main() {
print('1 senkron: ilk kurulus');
printAvatar(null);
scheduleMicrotask(() {
print('3 mikro gorev');
printAvatar(200);
});
Future(() {
print('4 gorev kuyrugu');
printAvatar(320);
});
print('2 senkron: main sonu');
}
1 senkron: ilk kurulus esnetilen: /Screen/Body/Avatar genislik=328 kisit=0..328 2 senkron: main sonu 3 mikro gorev secilen: /Screen/Body/Avatar genislik=200 kisit=0..328 4 gorev kuyrugu secilen: /Screen/Body/Avatar genislik=320 kisit=0..328
Numaralar kodun yazılış sırasına değil, çalışma sırasına aittir. main bitmeden hiçbir kuyruk işlenmez; bu yüzden 2 numaralı satır 3’ten önce basılır. İlk ölçümde değer yoktur ve Avatar esnetilendir. Mikro görev değeri 200 yapar, görev kuyruğundaki iş 320 yapar; ikisinde de istek aralığın içindedir ve Avatar seçilendir.
Hata ve Akış
Bir gelecek bir değerle değil, bir hatayla da tamamlanabilir. await o hatayı çağıran tarafa fırlatır ve try/catch onu yakalar. Aşağıdaki blokta yükleme başarısız olur, işlev yedek bir genişliğe döner ve ağaç bu genişlikle kurulur. Hata yakalandığı için programa yayılmaz; çıktıda yedek genişliği bildiren tek satırın ardından ağacın ölçümü yazılır.
// main.dart
import 'sizing.dart';
import 'avatar_body.dart';
Future<int> loadWidth() async {
throw StateError('baglanti yok');
}
Future<int> loadWidthOrFallback() async {
try {
return await loadWidth();
} catch (_) {
print('yukleme basarisiz, 96 kullanilir');
return 96;
}
}
Future<void> main() async {
final width = await loadWidthOrFallback();
printAvatar(width);
}
yukleme basarisiz, 96 kullanilir secilen: /Screen/Body/Avatar genislik=96 kisit=0..328
Gelecek tek bir değer taşır. Zamana yayılmış birden çok değer için Dart akış (Stream) tipini kullanır. async* ile işaretlenen bir işlev yield ile değerleri sırayla üretir; await for döngüsü her değeri geldiği sırayla alır.
// main.dart
import 'sizing.dart';
import 'avatar_body.dart';
Stream<int> widths() async* {
yield 200;
yield 320;
yield 400;
}
Future<void> main() async {
await for (final width in widths()) {
print('akis degeri: $width');
printAvatar(width);
}
}
akis degeri: 200 secilen: /Screen/Body/Avatar genislik=200 kisit=0..328 akis degeri: 320 secilen: /Screen/Body/Avatar genislik=320 kisit=0..328 akis degeri: 400 kirpilan: /Screen/Body/Avatar genislik=328 kisit=0..328
Her yeni değer ağacın yeniden ölçülmesini getirir. İlk iki değer 0..328 aralığında kalır ve seçilendir; üçüncüsü 400 olduğundan kısıt tarafından kırpılır. Aynı bileşende, aynı yerde, değer akarken boyutlanma sahibi değişir.
DB2. Akış modelde üç sabit değerden oluşur. Gerçek bir akış değerlerini zamana yayar; model yalnız sırayı korur ve değerler arasındaki bekleme yoktur.
Yalıtılmış İşlemci
Olay döngüsü işi bölüştürür ama paralel çalıştırmaz: ağır bir hesap döngüyü tutarsa kuyruklar işlenemez ve arayüz donar. Dart bunun için yalıtılmış işlemci (isolate) sunar. Yalıtılmış işlemci kendi belleği ve kendi olay döngüsü olan ayrı bir yürütme birimidir; ana birimle bellek paylaşmaz, yalnız ileti alışverişi yapar. Aşağıdaki parça ağır bir çözümlemeyi ayrı bir birime taşır. Parça ölçülmez; model yalıtılmış işlemciyi kurmaz.
// Platform kodu: yalitilmis islemci
import 'dart:isolate';
Future<int> parseInBackground(String raw) {
return Isolate.run(() => int.parse(raw));
}
Dönüş tipi yine bir Future<int>’tir. Çağıran taraf için fark yoktur: değer sonradan gelir ve ağaç, yukarıdaki iki ölçümde olduğu gibi, değer gelmeden ve geldikten sonra kurulur. Ağacın kuruluşu ana birimde kalır; yalıtılmış işlemci yalnız genişliği hesaplayan işi taşır.
Sınırlayıcı Ölçüm
Aşağıdaki blok bir bileşenin genişliği gelmeden önce ve geldikten sonra ağacın tamamını ölçer ve her ölçümün sayımını verir. Odak, ikinci ölçümdeki /Screen/Body/Avatar’dır.
// main.dart
import 'sizing.dart';
import 'avatar_body.dart';
Future<int> loadWidth() async => 200;
Future<void> main() async {
print('olcum 1: deger gelmeden once');
final before = measure(buildBody(null), 360);
printSizes(before);
countSizings(before);
final width = await loadWidth();
print('olcum 2: deger geldikten sonra');
final after = measure(buildBody(width), 360);
printSizes(after);
countSizings(after);
}
olcum 1: deger gelmeden once dayatilan: /Screen genislik=360 kisit=360..360 dayatilan: /Screen/Body genislik=328 kisit=328..328 secilen: /Screen/Body/Name genislik=120 kisit=0..328 esnetilen: /Screen/Body/Avatar genislik=328 kisit=0..328 dayatilan: 2 secilen: 1 kirpilan: 0 esnetilen: 1 ebeveyn: 3 olcum 2: deger geldikten sonra dayatilan: /Screen genislik=360 kisit=360..360 dayatilan: /Screen/Body genislik=328 kisit=328..328 secilen: /Screen/Body/Name genislik=120 kisit=0..328 secilen: /Screen/Body/Avatar genislik=200 kisit=0..328 dayatilan: 2 secilen: 2 kirpilan: 0 esnetilen: 0 ebeveyn: 2
Bu dersin boyutlanması: seçilen. Değer gelmeden Avatar istek bildirmez, sütun kısıtı 0..328 olduğundan alabildiği kadar genişler ve esnetilen olur. Değer gelince Avatar 200 ister, 200 aralığın içindedir ve genişliği Avatar seçer. İki ölçüm arasında ağaçta yalnız Avatar‘ın isteği değişmiştir ve boyutlanma bu tek düğümde el değiştirir. Sayım bunu özetler: ebeveyn 3’ten 2’ye iner.
Özet
- Bu dersin boyutlanması: seçilen. Değer gelmeden
/Screen/Body/Avataresnetilendir; 200 gelince isteği0..328aralığında kalır ve genişliğiAvatarseçer. Future<int>ileride tamamlanacak bir değerdir.asyncişlev gelecek döndürür,awaitdeğer gelene kadar işlevi bekletir ve çağıran taraf bu sırada devam eder.- Olay döngüsü senkron kodu bitirir, mikro görev kuyruğunu boşaltır, sonra görev kuyruğundan bir görev alır. Çıktıdaki numaralar bu sırayı gösterir.
- Başarısız bir gelecek
try/catchile yakalanıp yedek değere çevrilir. Akış zamana yayılmış değerler taşır ve her değer ağacı yeniden ölçtürür. - Yalıtılmış işlemci ağır işi ayrı bir bellekte yürütür ve sonucu yine bir gelecek olarak döndürür.
Sonraki Adım
Ağaç şimdiye kadar leaf, pad ve column çağrılarının bileşimiydi. Bileşen Kavramı dersi bu çağrıları çerçevenin bileşen kavramına bağlar: bir bileşenin ne olduğunu, ağaçta nasıl iç içe girdiğini ve bir kartın başka bir kısıtta neden başka bir sahiple karşılaştığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.