[Flutter翻譯]為什麼要在Flutter中使用隔離物?

Sunbreak發表於2021-03-16

image.png

原文地址:alphamikle.dev/why-should-…

原文作者:hashnode.com/@alphamikle

釋出時間:2021年3月16日

image.png

在Flutter中管理狀態的方法有很多,但大多數都是以這樣的方式構建的,所有的邏輯都是在應用程式的主隔離區中執行的。網路請求的執行,與WebSocket的合作,潛在的重同步操作(如本地搜尋),所有這些,作為一個規則,在主隔離區中實現。本文將展示其他門以及?。

我只看到了一個旨在將這些操作轉移到外部隔離區的包,但最近又出現了一個包(由我編寫)。我建議你熟悉一下它。

在這篇文章中,我將使用兩個主要術語--隔離體和主執行緒。它們的區別是為了讓文字不至於太過同義,但本質上主線也是一種隔離物。另外,在這裡你會發現一些表達方式會割傷耳朵(或眼睛)特別敏感的天性,所以我提前道歉--對不起。我會把所有有疑問的詞用斜體標出(不光是他們,現在就試著弄明白)。另外,進一步呼叫操作同步--我會記住,你會在呼叫第三方方法的同一個函式中收到結果。而非同步函式是指你不會在原地得到結果,而是在另一個函式中得到。

介紹

隔離器的設計是為了在您的Flutter應用程式的非主執行緒上執行程式碼。當主執行緒開始執行網路請求,執行計算,或做任何其他操作,除了它的主要目的--繪製介面,你遲早會面臨這樣一個事實,即渲染一幀的寶貴時間將開始增加。基本上,你在主執行緒上執行任何操作的時間被限制在16ms,這就是在60FPS下渲染2幀之間存在的視窗。不過,目前有很多手機的顯示頻率較高,由於我只有一臺,所以用不同的方法來比較同樣操作的應用的效能,就越有意思。本例中,視窗已經是11.11ms,顯示重新整理率為90FPS。

實驗一:初始條件

讓我們想象一下,你需要載入大量的資料,你可以通過幾種方式來實現。

  • 只要在主執行緒上提出一個請求
  • 使用計算函式進行請求
  • 明確使用隔離請求

實驗是在一臺搭載驍龍855處理器、螢幕頻率強制到90Hz的OnePlus 7 Pro上進行的。通過flutter run profile命令啟動應用程式。進行了從伺服器接收資料的模擬(連續10次同時請求5次)。

一次請求返回JSON--一個2273個元素的陣列,其中一個元素如截圖所示。答案的大小是1.12Mb。因此,對於5個同步請求,我們需要解析5.6Mb的JSON(但應用列表中會有2273個專案)。

image.png

我們從這樣的引數--幀渲染時間、操作時間、組織/編寫程式碼的複雜程度來比較三種方法。

第一個例子。一組來自主執行緒的請求

Future<void> loadItemsOnMainThread() async {
  _startFpsMeter();
  isLoading = true;
  notifyListeners();
  List<Item> mainThreadItems;
  for (int i = 0; i < 10; i++) {
    bench.startTimer('Load items in main thread');
    mainThreadItems = await makeManyRequests(5);
    final double diff = bench.endTimer('Load items in main thread');
    requestDurations.add(diff);
  }
  items.clear();
  items.addAll(mainThreadItems);
  isLoading = false;
  notifyListeners();
  _stopFpsMeter();
  requestDurations.clear();
}
複製程式碼

該方法駐留在應用程式主隔離區執行的反應狀態中。

該方法位於反應狀態下,在應用程式的主隔離區中執行。當執行上面的程式碼時,我們得到以下值。

  • 一幀的平均渲染時間(FrameRenderingTime)- 14.036ms / 71.25FPS。
  • 最大 FRT- 100.332ms / 9.97FPS
  • 執行5個併發請求的平均時間 - 226.894ms

請看實際操作。

www.youtube.com/watch?v=oRz…

第二個例子:compute()

Future<void> loadItemsWithComputed() async {
  _startFpsMeter();
  isLoading = true;
  notifyListeners();
  List<Item> computedItems;

  /// There were two variants of execution
  /// Each set of 5 concurrent requests, run sequentially,
  /// ran in compute function
  if (true) {
    for (int i = 0; i < 10; i++) {
      bench.startTimer('Load items in computed');
      computedItems = await compute<dynamic, List<Item>>(_loadItemsWithComputed, null);
      final double diff = bench.endTimer('Load items in computed');
      requestDurations.add(diff);
    }

    /// The second option is all 10 requests of 5 in one compute function
  } else {
    bench.startTimer('Load items in computed');
    computedItems = await compute<dynamic, List<Item>>(_loadAllItemsWithComputed, null);
    final double diff = bench.endTimer('Load items in computed');
    requestDurations.add(diff);
  }
  items.clear();
  items.addAll(computedItems);
  isLoading = false;
  notifyListeners();
  _stopFpsMeter();
  requestDurations.clear();
}

Future<List<Item>> _loadItemsWithComputed([dynamic _]) async {
  return makeManyRequests(5);
}

Future<List<Item>> _loadAllItemsWithComputed([dynamic _]) async {
  List<Item> items;
  for (int i = 0; i < 10; i++) {
    items = await makeManyRequests(5);
  }
  return items;
}
複製程式碼

在這個例子中,同樣的請求在兩個版本中發起:在10個連續請求中,每5個併發請求都在自己的計算中發起。

  • 平均FRT - 11.254ms / 88.86FPS。
  • 最大 FRT - 22.304ms / 44.84FPS
  • 執行5個併發請求的平均時間 - 386.253ms

第二種方式--5個同時請求的10個順序請求都在一次計算中發起。

  • 平均FRT - 11.252ms / 88.87FPS。
  • 最大 FRT - 22.306ms / 44.83FPS
  • 5個併發請求的平均時間(計算中執行5個請求中的全部10個,除以10)-231.747ms。

www.youtube.com/watch?v=bCI…

第三個例子。隔離

這裡值得做一個題外話:在包的術語中,一般狀態(state)有兩個部分。

  • 前端狀態是一個任何反應式的狀態,它向後端傳送訊息,處理它的響應,也儲存資料,更新後UI會被更新,它也儲存從UI呼叫的輕量方法。這個狀態工作在應用程式的主執行緒中。
  • Backend-state是一個重狀態,它接收來自Frontend的訊息,執行重操作,向Frontend返回響應,並在一個單獨的隔離區工作。這個狀態也可以儲存資料(無論你在哪裡)。

由於需要與隔離區進行通訊,第三種方案的程式碼被拆分成幾個方法。前端方法如下圖所示。

/// This method is the entry point to the operation
Future<void> loadItemsWithIsolate() async {
  /// We start the frame counter before the whole operation
  _startFpsMeter();
  isLoading = true;
  notifyListeners();

  /// We start counting the request time
  bench.startTimer('Load items in separate isolate');

  /// Sending an event to Backend
  send(Events.startLoadingItems);
}

/// The [Events.loadingItems] event handler for updating the request time from the isolate
void _middleLoadingEvent() {
  final double time = bench.endTimer('Load items in separate isolate');
  requestDurations.add(time);
  bench.startTimer('Load items in separate isolate');
}

/// The [Events.endLoadingItems] terminating event handler from the isolate
Future<void> _endLoadingEvents(List<Item> items) async {
  this.items.clear();

  /// Updating data in reactive state
  this.items.addAll(items);

  /// Finishing counting request times
  final double time = bench.endTimer('Load items in separate isolate');
  requestDurations.add(time);
  isLoading = false;
  notifyListeners();

  /// Stop the frame counter
  _stopFpsMeter();
  requestDurations.clear();
}
複製程式碼

在這裡你可以看到後端方法,以及我們需要的邏輯。

/// Event handler [Events.startLoadingItems]
Future<void> _loadingItems() async {
  _items.clear();
  for (int i = 0; i < 10; i++) {
    _items.addAll(await makeManyRequests(5));
    if (i < (10 - 1)) {
      /// For all requests except the last one - we send only one event
      send(Events.loadingItems);
    } else {
      /// For the last of 10 requests - send a message with data
      send(Events.endLoadingItems, _items);
    }
  }
}
複製程式碼

結果。

  • 平均FRT - 11.151ms / 89.68FPS
  • 最大 FRT - 11.152ms / 89.67FPS

www.youtube.com/watch?v=Bmk…

小計

在應用中進行三次載入同一資料集的實驗後,我們得到以下結果。

image.png

從這些結果來看,可以得出以下結論。

  • Flutter能夠穩定地提供90FPS的畫面
  • 在您的應用程式的主執行緒上進行大量的網路請求會影響其效能--出現延遲。
  • 編寫在主執行緒上執行的程式碼,就像剝梨子一樣容易。
  • 計算允許你減少滯後的可見性。
  • 使用Compute寫程式碼有一定的侷限性(純函式,靜態方法不能傳遞,不能用閉包等)。
  • 按操作時間計算,使用compute時的開銷~150-160ms。
  • 隔離完全消除滯後
  • 使用隔離體編寫程式碼是比較困難的,而且也有一些侷限性,這一點將在後面討論。

讓我們再進行一次實驗,以確定哪種方法對所研究的所有引數都是最優的。

實驗二:區域性搜尋

讓我們想象一下,現在我們需要通過輸入的值在載入的資料中找到某些元素。這個測試是這樣實現的:有一個輸入,從專案列表中可用的子串數中逐字輸入3個字元的子串。用於搜尋的陣列中的元素數量增加了10倍,為22730個。

搜尋以2種模式進行--從列表中的元素中輸入字串的原始存在,以及使用字串相似性演算法

另外,非同步搜尋選項--compute / Isolate,在前一個搜尋完成之前不會啟動。它的工作原理是這樣的--在輸入欄位中輸入第一個字元,我們就開始搜尋,直到搜尋完成--資料不會返回主執行緒,UI也不會重新繪製,第二個字元不會在輸入欄位中輸入。當所有操作完成後,輸入第二個字元,也反之。這類似於我們 "儲存 "使用者輸入的字元時的演算法,然後只傳送一個請求,而不是為絕對每一個輸入的字元傳送一個請求,不管它們輸入得多快。

渲染時間只在字元輸入搜尋時進行測量,即資料準備操作,其他任何事情都不會影響收集到的資料。

對於初學者來說,幫助函式,搜尋函式和其他通用程式碼。

/// Function for creating a copy of elements
/// used as source for filtering
void cacheItems() {
  _notFilteredItems.clear();
  final List<Item> multipliedItems = [];
  for (int i = 0; i < 10; i++) {
    multipliedItems.addAll(items);
  }
  _notFilteredItems.addAll(multipliedItems);
}
複製程式碼
/// Function that launches a test script
/// for entering characters into a text input
Future<void> _testSearch() async {
  List<String> words = items.map((Item item) => item.profile.replaceAll('https://opencollective.com/', '')).toSet().toList();
  words = words
      .map((String word) {
        final String newWord = word.substring(0, min(word.length, 3));
        return newWord;
      })
      .toSet()
      .take(3)
      .toList();

  /// Start the frame counter
  _startFpsMeter();
  for (String word in words) {
    final List<String> letters = word.split('');
    String search = '';
    for (String letter in letters) {
      search += letter;
      await _setWord(search);
    }
    while (search.isNotEmpty) {
      search = search.substring(0, search.length - 1);
      await _setWord(search);
    }
  }

  /// Stop the frame counter
  _stopFpsMeter();
}
複製程式碼
/// We enter characters with a delay of 800ms,
/// but if the data from the asynchronous filter (computed / isolate)
/// has not yet arrived, then we are waiting for them
Future<void> _setWord(String word) async {
  if (!canPlaceNextLetter) {
    await wait(800);
    await _setWord(word);
  } else {
    searchController.value = TextEditingValue(text: word);
    await wait(800);
  }
}
複製程式碼
/// Depending on the set flag [USE_SIMILARITY]
/// whether or not search with string similarity is used
List<Item> filterItems(Packet2<List<Item>, String> itemsAndInputValue) {
  return itemsAndInputValue.value.where((Item item) {
    return item.profile.contains(itemsAndInputValue.value2) || (USE_SIMILARITY && isStringsSimilar(item.profile, itemsAndInputValue.value2));
  }).toList();
}

bool isStringsSimilar(String first, String second) {
  return max(StringSimilarity.compareTwoStrings(first, second), StringSimilarity.compareTwoStrings(second, first)) >= 0.3);
}
複製程式碼

在主線中搜尋

Future<void> runSearchOnMainThread() async {
  cacheItems();
  isLoading = true;
  notifyListeners();
  searchController.addListener(_searchOnMainThread);
  await _testSearch();
  searchController.removeListener(_searchOnMainThread);
  isLoading = false;
  notifyListeners();
}

void _searchOnMainThread() {
  final String searchValue = searchController.text;
  if (searchValue.isEmpty && items.length != _notFilteredItems.length) {
    items.clear();
    items.addAll(_notFilteredItems);
    notifyListeners();
    return;
  }
  items.clear();

  /// Packet2 - wrapper class for two values
  items.addAll(filterItems(Packet2(_notFilteredItems, searchValue)));
  notifyListeners();
}
複製程式碼

這就是它的樣子。

www.youtube.com/watch?v=Vlc…

www.youtube.com/watch?v=XpT…

簡單的搜尋結果。

  • 平均FRT - 21.588ms / 46.32FPS。
  • 最大 FRT - 668,986ms / 1.50FPS

用相似性結果搜尋。

  • 平均FRT - 43,123ms / 23.19FPS。
  • 最大 FRT - 2 440,910ms / 0.41FPS

用compute()搜尋

Future<void> runSearchWithCompute() async {
  cacheItems();
  isLoading = true;
  notifyListeners();
  searchController.addListener(_searchWithCompute);
  await _testSearch();
  searchController.removeListener(_searchWithCompute);
  isLoading = false;
  notifyListeners();
}

Future<void> _searchWithCompute() async {
  canPlaceNextLetter = false;

  /// Before starting filtering, set a flag that will signal
  /// that asynchronous filtering is taking place
  isSearching = true;
  notifyListeners();
  final String searchValue = searchController.text;
  if (searchValue.isEmpty && items.length != _notFilteredItems.length) {
    items.clear();
    items.addAll(_notFilteredItems);
    isSearching = false;
    notifyListeners();
    await wait(800);
    canPlaceNextLetter = true;
    return;
  }
  final List<Item> filteredItems = await compute(filterItems, Packet2(_notFilteredItems, searchValue));

  /// After filtering is finished, remove the signal
  isSearching = false;
  notifyListeners();
  await wait(800);
  items.clear();
  items.addAll(filteredItems);
  notifyListeners();
  canPlaceNextLetter = true;
}
複製程式碼

一些YouTube。

www.youtube.com/watch?v=ecv…

www.youtube.com/watch?v=yv2…

簡單的搜尋結果。

  • 平均FRT - 12.682ms / 78.85FPS。
  • 最大 FRT - 111.544ms / 8.97FPS

用相似性結果搜尋。

  • 平均FRT - 12.515ms / 79.90FPS。
  • 最大 FRT - 111,527ms / 8.97FPS

用Isolate搜尋

程式碼不多。前端

/// Start operation in isolate by sending message
Future<void> runSearchInIsolate() async {
  send(Events.cacheItems);
}

void _middleLoadingEvent() {
  final double time = bench.endTimer('Load items in separate isolate');
  requestDurations.add(time);
  bench.startTimer('Load items in separate isolate');
}

/// This method will called on event [Events.cacheItems], which will sent by Backend
Future<void> _startSearchOnIsolate() async {
  isLoading = true;
  notifyListeners();
  searchController.addListener(_searchInIsolate);
  await _testSearch();
  searchController.removeListener(_searchInIsolate);
  isLoading = false;
  notifyListeners();
}

/// On every input event we send message to Backend
void _searchInIsolate() {
  canPlaceNextLetter = false;
  isSearching = true;
  notifyListeners();
  send(Events.startSearch, searchController.text);
}

/// Writing data from Backend (isolate) to Frontend (reactive state)
Future<void> _setFilteredItems(List<Item> filteredItems) async {
  isSearching = false;
  notifyListeners();
  await wait(800);
  items.clear();
  items.addAll(filteredItems);
  notifyListeners();
  canPlaceNextLetter = true;
}

Future<void> _endLoadingEvents(List<Item> items) async {
  this.items.clear();
  this.items.addAll(items);
  final double time = bench.endTimer('Load items in separate isolate');
  requestDurations.add(time);
  await wait(800);
  isLoading = false;
  notifyListeners();
  _stopFpsMeter();
  print('Load items in isolate ->' + requestDurations.join(' ').replaceAll('.', ','));
  requestDurations.clear();
}
複製程式碼

而這些方法都是在第三方隔離區執行的後臺。

/// Handler for event [Events.cacheItems]
void _cacheItems() {
  _notFilteredItems.clear();
  final List<Item> multipliedItems = [];
  for (int i = 0; i < 10; i++) {
    multipliedItems.addAll(_items);
  }
  _notFilteredItems.addAll(multipliedItems);
  send(Events.cacheItems);
}

/// For each event [Events.startSearch] this method is called,
/// filtering elements and sending the filtered to the light state
void _filterItems(String searchValue) {
  if (searchValue.isEmpty) {
    _items.clear();
    _items.addAll(_notFilteredItems);
    send(ThirdEvents.setFilteredItems, _items);
    return;
  }
  final List<Item> filteredItems = filterItems(Packet2(_notFilteredItems, searchValue));
  _items.clear();
  _items.addAll(filteredItems);
  send(Events.setFilteredItems, _items);
}
複製程式碼

簡單的搜尋結果。

  • 平均FRT - 11.354ms / 88.08FPS
  • 最大 FRT - 33.455ms / 29.89FPS

用相似性搜尋。

  • 平均FRT - 11.353ms / 88.08FPS。
  • 最大 FRT - 33.459ms / 29.89FPS

更多結論

image.png

從這個平板電腦和之前的研究來看。

  • 主線不應該用於大於16ms的操作(至少提供60FPS)。
  • Compute在技術上適用於頻繁和大量的操作,但會帶來同樣150ms的開銷,而且與永久隔離相比,效能也更不穩定(這可能是由於每次開啟,和操作完成後,隔離都會被關閉 ,這也需要資源)。
  • 在Flutter應用中,隔離是實現最大效能的最難的程式碼方式。

隔離器

好吧,看來隔離是實現這個結果的理想方式,連Google都建議所有的重度操作都使用隔離(這是為了口口相傳,我沒有找到任何證明)。但是你必須要寫很多程式碼。事實上,上面所寫的一切都是使用最開始介紹的庫實現的結果,如果沒有它--你將不得不寫很多很多。另外,這個搜尋演算法還可以進行優化--在過濾完所有元素後,只傳送一小部分資料到前面--這樣會佔用更少的資源,在其傳輸後,再傳送其他所有的資料。或者按塊傳送資料。

我還對隔離體之間的通訊通道的頻寬進行了實驗。為了評估它,使用了以下實體。

class Item {
  const Item(
    this.id,
    this.createdAt,
    this.profile,
    this.imageUrl,
  );

  final int id;
  final DateTime createdAt;
  final String profile;
  final String imageUrl;
}
複製程式碼

而結果是--同時傳輸5000個元素,複製資料所需的時間並不影響UI,即渲染頻率並沒有降低。1,000,000個這樣的元素,通過Future.delayed分批傳輸,每次5000個,在8ms的突發傳輸之間強制暫停,而幀率並沒有下降到80FPS以下。遺憾的是,我在寫這篇文章之前很久就做了這個實驗,沒有乾貨資料(如果有要求,會出現)。

很多人可能覺得處理隔離器很困難或者沒有必要,大家就止步於計算。在這裡,這個包的另一個功能可以派上用場,它把API等同於計算的簡單性,結果,它給了更多的可能性。

下面是一個例子。

/// Frontend part
Future<void> decrement([int diff = 1]) async {
  counter = await runBackendMethod<int, int>(Events.decrement, diff);
}

/// -----

/// Backend part
Future<int> _decrement(int diff) async {
  counter -= diff;
  return counter;
}
複製程式碼

由於這種方法,你可以簡單地通過這個函式對應的ID來呼叫後臺函式。ID與方法的匹配是在預定義的getter中指定的。

/// Frontend part
/// This block is responsible for handling events from the isolate.
@override
Map<Events, Function> get tasks => {
      Events.increment: _setCounter,
      Events.decrement: _setCounter,
      Events.error: _setCounter,
    };

/// -----

/// Backend part
/// And this one is for handling events from the main thread
@override
Map<Events, Function> get operations => {
      Events.increment: _increment,
      Events.decrement: _decrement,
    };
複製程式碼

因此,我們得到兩種互動方式。

  1. 通過明確的訊息傳遞進行非同步通訊

前端使用send方法向後端傳送一個事件,在訊息中傳遞事件ID和一個可選的引數。

enum Events {
  increment,
}

class FirstState with Frontend<Events> {
  int counter = 0;

  void increment([int diff = 1]) {
    send(Events.increment, diff);
  }

  void _setCounter(int value) {
    counter = value;
    notifyListeners();
  }

  @override
  Map<Events, Function> get tasks => {
    Events.increment: _setCounter,
  };
}
複製程式碼

該訊息被傳遞到後臺並在那裡進行處理。

class FirstBackend extends Backend<Events, void> {
  FirstBackend(BackendArgument<void> argument) : super(argument);

  int counter = 0;

  void _increment(int diff) {
    counter += diff;
    send(Events.increment, counter);
  }

  @override
  Map<Events, Function> get operations => {
    Events.increment: _increment,
  };
}
複製程式碼

後臺將結果返回給前臺就可以了! 返回結果有兩種方式--通過使用後臺方法(return)返回響應(那麼響應將以與接收到的訊息ID相同的訊息傳送),第二種是顯式呼叫傳送方法。在這種情況下,你可以用你指定的任何ID向反應狀態傳送任何訊息。最主要的是,處理方法是由這些ID設定的。

從原理上看,第一種方式是這樣的。

image.png

黃色雙面箭頭--與外界的任何服務進行互動,例如,某個伺服器。而紫色的,從伺服器到後端--這些是來自同一伺服器的傳入訊息,例如--WebSocket。

  1. 通過呼叫後端函式的ID來實現同步通訊

前端使用runBackendMethod方法,指定一個ID來呼叫對應的後端方法,直接得到響應。這樣一來,甚至不需要在你的front的任務列表中指定什麼。同時,如下面的程式碼所示,你可以在你的前端覆蓋onBackendResponse方法,每次你的前端狀態收到來自後端的訊息時,都會呼叫這個方法。

enum Events {
  decrement,
}

class FirstState with ChangeNotifier, Frontend<Events> {
  int counter = 0;

  Future<void> decrement([int diff = 1]) async {
    counter = await runBackendMethod<int, int>(Events.decrement, diff);
  }

  /// Automatically notification after any event from backend
  @override
  void onBackendResponse() {
    notifyListeners();
  }
}
複製程式碼

後面的方法對傳入的事件進行處理,並簡單地返回結果。在這種情況下,有一個限制--被稱為 "同步 "的後端方法不應該呼叫它們所對應的相同ID的傳送方法。在這個例子中,_decrement方法不應該呼叫傳送(Events.decrement)方法。同時,他可以傳送任何其他訊息。

class FirstBackend extends Backend<Events, void> {
  FirstBackend(BackendArgument<void> argument) : super(argument);

  int counter = 0;

  /// Or, you can simply return a value
  Future<int> _decrement(int diff) async {
    counter -= diff;
    return counter;
  }

  @override
  Map<Events, Function> get operations => {
    Events.decrement: _decrement,
  };
}
複製程式碼

第二種方式的方案與第一種類似,只是在前面你不需要寫來自後面的事件處理程式。

image.png

很快,在0.0.5版本中,這個功能就可以工作了,並且在後退中--你可以從它的後臺以同步模式執行Frontend的任務。

還有什麼要新增的...

要使用這樣的捆綁,你需要建立這些後端。為此,Frontend<EventType>有一個後端建立機制--initBackend方法。在這個方法中,你需要傳遞一個工廠函式來建立後端。它應該是一個純頂層函式(Flutter文件中說的頂層),或者是一個靜態類方法。建立一個隔離體的時間大約是200ms。

enum Events {
  increment,
  decrement,
}

class FirstState with ChangeNotifier, Frontend<Events> {
  int counter = 0;

  void increment([int diff = 1]) {
    send(Events.increment, diff);
  }

  Future<void> decrement([int diff = 1]) async {
    counter = await runBackendMethod<int, int>(Events.decrement, diff);
  }

  void _setCounter(int value) {
    counter = value;
  }

  Future<void> initState() async {
    await initBackend(createFirstBackend);
  }

  /// Automatically notification after any event from backend
  @override
  void onBackendResponse() {
    notifyListeners();
  }

  @override
  Map<Events, Function> get tasks => {
    Events.increment: _setCounter,
  };
}
複製程式碼

後臺部件建立函式的一個例子。

typedef Creator<TDataType> = void Function(BackendArgument<TDataType> argument);

void createFirstBackend(BackendArgument<void> argument) {
  FirstBackend(argument.toFrontend);
}

@protected
Future<void> initBackend<TDataType extends Object>(Creator<TDataType> creator, {TDataType data, ErrorHandler errorHandler}) async {
  /// ...
}
複製程式碼

侷限性

  • 一切都和普通隔離劑一樣
  • 對於每一個正在建立的 "後端",當前都在建立自己的隔離區,如果後端太多,它們的建立時間就會變得很明顯,特別是當你初始化所有的後端時,比如說在載入應用程式的時候。我實驗過同時執行30個後端--上述手機在--釋放模式下的啟動時間需要6秒多。
  • 在處理隔離體(後端)出現的錯誤時,存在一些困難。在這裡,如果你對這個包感興趣,你應該更詳細地熟悉Frontend的initBackend方法。
  • 與只在主執行緒中儲存邏輯相比,編寫程式碼的複雜度較高

使用清單

這裡的一切都很簡單,你不需要使用隔離劑(無論是單獨使用還是使用這個包),如果。

  • 你的應用程式在各種操作下的效能都不會下降。
  • 對於瓶頸問題,計算就夠了。
  • 你不會想處理隔離物吧?
  • 你的應用程式的生命週期很短,所以沒有必要優化它。

否則,你可以將注意力轉向這種方法和一個(稱為Isolator),它將簡化你對隔離物的工作。

本文的所有例子都可以在Github上找到。


通過www.DeepL.com/Translator(免費版)翻譯

相關文章