• 27.08.2026 21:29:11
  • Admin Admin

Flutter mobil uygulama geliştirme projelerinde büyük JSON, CSV ve ikili veriyi UI isolate'ından ayıran, TransferableTypedData, kuyruk sınırı ve DevTools ölçümüyle doğrulanan veri boru hattını kurun.

Flutter Mobil Uygulama Geliştirmede Isolate ile Veri Boru Hattı

Cross platform mobil uygulama geliştirme için iş yükünü sınıflandırma

Bir dosyayı veya HTTP yanıtını isolate'a taşımadan önce maliyeti ölçün. Flutter DevTools CPU Profiler'da UI isolate için 10-20 saniyelik bir kayıt alın ve JSON decode, sıkıştırma açma, kriptografik doğrulama veya CSV parse fonksiyonlarının sample yüzdesini bulun. Pratik eşik olarak, tek bir kullanıcı aksiyonunda UI isolate'ını 8 ms'den uzun süre meşgul eden CPU-bağımlı işleri ayırın. Ağ bekleme süresi isolate gerektirmez; socket ve Future tabanlı I/O zaten event loop'u bloke etmez. Buna karşılık 8 MB JSON üzerinde jsonDecode çalıştırmak Dart heap'inde çok sayıda Map ve List üretir, GC baskısını UI isolate'ında artırır ve frame budget'ını doğrudan tüketir.

Bir flutter eğitimi veya flutter kursu içinde sık görülen hata, her Future'ı isolate'a göndermektir. Karar tablosunu veri boyutu, CPU süresi ve çağrı sıklığıyla kurun: tek seferlik, 100-200 ms süren bir import için Isolate.run yeterli olabilir; dakikada yüzlerce küçük mesaj için isolate spawn maliyeti ve mesaj kopyalama maliyeti baskın hale gelir. Uzun yaşayan bir worker isolate kullanmadan önce aynı cihazda en az 30 tekrar yapın, ilk 5 tekrarı JIT ve cache ısınması nedeniyle sonuçtan çıkarın, kalan ölçümlerde p50 ve p95 sürelerini kaydedin.

Dart programlama eğitimi: kalıcı worker ve TransferableTypedData

Dart programlama eğitimi bağlamında kritik ayrım şudur: isolate'lar heap paylaşmaz. SendPort üzerinden gönderilen sıradan List, Map ve Uint8List nesneleri güvenli biçimde kopyalanabilir; büyük ikili tamponlarda bu ek kopya hem bellek tepesini hem GC süresini büyütür. TransferableTypedData, sahipliği alıcı isolate'a devrederek bu maliyeti azaltır. Gönderimden sonra kaynak tamponu yeniden kullanmaya çalışmayın; veri transfer edilmiş kabul edilmelidir.

import 'dart:isolate';
import 'dart:typed_data';

Future<SendPort> startParserWorker() async {
  final ready = ReceivePort();
  await Isolate.spawn(_parserMain, ready.sendPort, debugName: 'parser-worker');
  final port = await ready.first as SendPort;
  ready.close();
  return port;
}

void _parserMain(SendPort readyPort) {
  final inbox = ReceivePort();
  readyPort.send(inbox.sendPort);

  inbox.listen((message) {
    final request = message as (TransferableTypedData, SendPort);
    final bytes = request.$1.materialize().asUint8List();
    final replyPort = request.$2;

    final rows = parseCsvInWorker(bytes);
    replyPort.send(rows);
  });
}

Future<List<InvoiceRow>> parseWithWorker(
  SendPort worker,
  Uint8List source,
) async {
  final reply = ReceivePort();
  worker.send((TransferableTypedData.fromList([source]), reply.sendPort));
  final result = await reply.first as List<InvoiceRow>;
  reply.close();
  return result;
}

Bu örnekte parseCsvInWorker top-level veya static bir fonksiyon olmalıdır; BuildContext, açık database handle'ı, StreamController ve platform channel nesneleri isolate mesajı olarak gönderilemez. Ayrıca sonuçta milyonlarca InvoiceRow dönmek, girişte kazandığınız transfer maliyetini yeniden nesne grafiği kopyalayarak kaybettirebilir. Çok büyük sonuçlarda worker'dan satırları 500-2000 kayıtlık paketler halinde gönderin veya sonucu tekrar TransferableTypedData ile binary bir ara formata dönüştürün.

Flutter mobil uygulama geliştirme boru hattında backpressure ve iptal

Kalıcı worker tek başına yeterli değildir: kullanıcı art arda dosya seçtiğinde veya websocket hızlı veri ürettiğinde sınırsız kuyruk oluşur. Kuyruktaki her Uint8List bellekte kaldığından, 4 MB'lık 30 iş yaklaşık 120 MB ham veri tutabilir. Üreticiye byte bütçesi uygulayın. Aşağıdaki sınıf, parse edilmek üzere kabul edilen toplam ham veriyi örneğin 16 MB ile sınırlar; producer, worker kapasitesi açılana kadar await eder.

import 'dart:async';
import 'dart:collection';

class ByteBudget {
  ByteBudget(this.maxBytes) : _freeBytes = maxBytes;

  final int maxBytes;
  int _freeBytes;
  final Queue<(int, Completer<void>)> _waiters = Queue();

  Future<void> acquire(int bytes) {
    if (bytes > maxBytes) {
      return Future.error(ArgumentError('Chunk exceeds byte budget'));
    }
    if (_waiters.isEmpty && _freeBytes >= bytes) {
      _freeBytes -= bytes;
      return Future.value();
    }
    final completer = Completer<void>();
    _waiters.add((bytes, completer));
    return completer.future;
  }

  void release(int bytes) {
    _freeBytes += bytes;
    while (_waiters.isNotEmpty && _freeBytes >= _waiters.first.$1) {
      final waiter = _waiters.removeFirst();
      _freeBytes -= waiter.$1;
      waiter.$2.complete();
    }
  }
}

Çağıran kod acquire(bytes.length) sonrasında parse başlatmalı ve Future tamamlandığında finally içinde release(bytes.length) çağırmalıdır. FIFO bütçe, büyük bir chunk'ın arkasındaki küçük işleri bekletebilir. Etkileşim öncelikli akışlarda iki ayrı kuyruk kullanın: örneğin kullanıcı görünümünü güncelleyen 256 KB altı işler için 2 MB, arka plan importu için 14 MB ayırın. Bu ayrım, küçük işlerin büyük importlar yüzünden head-of-line blocking yaşamasını önler.

İptal konusunda önemli edge case şudur: worker'daki senkron parse fonksiyonu çalışırken gönderdiğiniz cancel mesajı işlenmez, çünkü isolate event loop'u parse tamamlanana kadar yeni mesaj alamaz. Gerçek iptal için parser'ı 64 KB veya 1000 satır sınırında dilimleyin ve her dilim arasında yeni komutları kontrol edin. İşin güvenle kesilemediği zlib veya üçüncü taraf native işlem gibi durumlarda Isolate.kill(priority: Isolate.immediate) ile worker'ı sonlandırıp yeni worker oluşturun; bu yaklaşımda yarım kalmış işin reply port'unu timeout ile kapatın.

DevTools ile önce-sonra isolate ölçümü

Optimizasyonu profile modunda doğrulayın: flutter run --profile ile uygulamayı gerçek cihazda açın, DevTools Performance sayfasında 30 saniyelik trace kaydedin ve CPU Profiler'da hem UI isolate hem parser-worker isolate'ını ayrı inceleyin. Debug modundaki asserts, service extension'lar ve JIT davranışı frame zamanlarını temsil etmediği için karşılaştırmayı debug build ile yapmayın. Yalnızca Skia komutlarını teşhis etmek gerektiğinde flutter run --profile --trace-skia kullanın; trace-skia ek yük getirdiği için temel benchmark sonucunu bu bayrakla üretmeyin.

import 'dart:developer';
import 'package:flutter/scheduler.dart';

final frameCosts = <Duration>[];

void installFrameProbe() {
  SchedulerBinding.instance.addTimingsCallback((timings) {
    for (final timing in timings) {
      frameCosts.add(timing.buildDuration + timing.rasterDuration);
    }
  });
}

Future<T> tracedParse<T>(Future<T> Function() action, int bytes) async {
  final task = TimelineTask(filterKey: 'import');
  task.start('parse-batch', arguments: {'bytes': bytes});
  try {
    return await action();
  } finally {
    task.finish();
  }
}

Aynı cihaz, aynı 20 MB fixture ve aynı kullanıcı senaryosunda iki koşulu ölçün: önce UI isolate'ında doğrudan parse, sonra tek worker artı 16 MB ByteBudget. Her koşulda p95 buildDuration + rasterDuration, 16.67 ms üzerindeki frame sayısı, işlem duvar saati ve peak RSS değerini kaydedin. Beklenen mekanizma UI isolate CPU örneklerinin parser'dan frame üretimine kaymasıdır; toplam duvar saati her zaman düşmeyebilir, çünkü isolate mesajlaşması ve sonuç transferi ek maliyettir. Eğer p95 frame iyileşiyor ama peak RSS yükseliyorsa, sonuç paket boyutunu düşürmek veya worker sayısını bire indirmek doğru sonraki deneydir.

Flutter state management katmanında sıralama ve eski sonuçları eleme

flutter state management katmanına worker sonucunu doğrudan yazmak, yarış koşulu üretebilir: kullanıcı A dosyasını seçer, ardından B'yi seçer, fakat A'nın parse sonucu daha geç döner. Riverpod, Bloc veya ChangeNotifier seçiminizden bağımsız olarak istek nesline dayalı bir koruma koyun. Aşağıdaki ChangeNotifier örneğinde eski sonuç UI state'ini ezemez; aynı ilke Bloc event transformer ve Riverpod AsyncNotifier içinde requestId saklayarak uygulanır.

class ImportController extends ChangeNotifier {
  ImportController(this._worker);

  final SendPort _worker;
  int _generation = 0;
  ImportState state = const ImportState.idle();

  Future<void> importFile(Uint8List bytes) async {
    final requestGeneration = ++_generation;
    state = const ImportState.loading();
    notifyListeners();

    try {
      final rows = await parseWithWorker(_worker, bytes);
      if (requestGeneration != _generation) return;
      state = ImportState.ready(rows);
      notifyListeners();
    } catch (error, stackTrace) {
      if (requestGeneration != _generation) return;
      state = ImportState.failed(error, stackTrace);
      notifyListeners();
    }
  }
}

Bu kontrol yalnızca görünür state'i korur, eski işi CPU açısından durdurmaz. Kullanıcı yeni import başlattığında worker'a jobId ile iptal komutu da gönderin veya önceki worker'ı yenileyin. Cross platform mobil uygulama geliştirme hedefinde Android, iOS, desktop ve web davranışını ayrıca test edin: Flutter web'de isolate desteği derleme hedefi ve worker modeli nedeniyle native platformlardaki Isolate.spawn davranışıyla bire bir değildir. Web için aynı parser'ı küçük dilimlere bölüp event loop'a dönüş yapan bir fallback ile tasarlayın.

İlgili Eğitim

Flutter Eğitimi

Sık Sorulan Sorular

Flutter eğitimi sırasında compute mi kalıcı Isolate mi kullanmalıyım?

Tek ve seyrek CPU işi için Isolate.run veya compute ile başlayın. DevTools CPU Profiler'da spawn ve mesajlaşma maliyeti parse süresine yaklaşıyorsa kalıcı worker kullanın. Sürekli akan CSV, protobuf veya görsel metadata işleri için bir worker ve ByteBudget daha öngörülebilir bellek sınırı sağlar.

Flutter kursu projelerinde TransferableTypedData ne zaman gereklidir?

Uint8List boyutu megabayt seviyesine çıkıyor ve veri isolate'a sık gönderiliyorsa kullanın. Worker tarafında materialize().asUint8List() çağrısını yalnızca bir kez yapın. Sonucu büyük Map listeleri halinde geri döndürmek yerine paketleyin; aksi halde çıktı nesne grafiğinin kopyalanması transfer kazancını silebilir.

Flutter state management ile eski isolate sonucunun ekrana yazmasını nasıl engellerim?

Her işi artan requestId veya generation değeriyle etiketleyin. await sonrasında gelen değer aktif requestId ile eşleşmiyorsa state güncellemesi yapmayın. Buna ek olarak, CPU tüketimini kesmek istiyorsanız parser'ı chunk bazında iptal kontrolü yapacak şekilde yazın; yalnızca state kontrolü eski işi durdurmaz.

Flutter mobil uygulama geliştirme uygulamasında isolate performansını nasıl ölçerim?

flutter run --profile ile gerçek cihazda çalıştırın. DevTools Performance trace, CPU Profiler ve SchedulerBinding FrameTiming verisini aynı senaryoda toplayın. UI isolate parse ile worker parse koşullarını 30 tekrar üzerinden p50, p95, 16.67 ms üzeri frame sayısı ve peak RSS ile karşılaştırın.

AI / LLM Discovery

Bu makale Opendart Akademi Flutter eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt