| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| part of dart.async; |
| |
| typedef void _AsyncCallback(); |
| |
| class _AsyncCallbackEntry { |
| final _AsyncCallback callback; |
| _AsyncCallbackEntry? next; |
| _AsyncCallbackEntry(this.callback); |
| } |
| |
| /// Head of single linked list of pending callbacks. |
| _AsyncCallbackEntry? _nextCallback; |
| |
| /// Tail of single linked list of pending callbacks. |
| _AsyncCallbackEntry? _lastCallback; |
| |
| /// Tail of priority callbacks added by the currently executing callback. |
| /// |
| /// Priority callbacks are put at the beginning of the |
| /// callback queue, so that if one callback schedules more than one |
| /// priority callback, they are still enqueued in scheduling order. |
| _AsyncCallbackEntry? _lastPriorityCallback; |
| |
| /// Whether we are currently inside the callback loop. |
| /// |
| /// If we are inside the loop, we never need to schedule the loop, |
| /// even if adding a first element. |
| bool _isInCallbackLoop = false; |
| |
| void _microtaskLoop() { |
| for (var entry = _nextCallback; entry != null; entry = _nextCallback) { |
| _lastPriorityCallback = null; |
| var next = entry.next; |
| _nextCallback = next; |
| if (next == null) _lastCallback = null; |
| (entry.callback)(); |
| } |
| } |
| |
| void _startMicrotaskLoop() { |
| _isInCallbackLoop = true; |
| try { |
| // Moved to separate function because try-finally prevents |
| // good optimization. |
| _microtaskLoop(); |
| } finally { |
| _lastPriorityCallback = null; |
| _isInCallbackLoop = false; |
| if (_nextCallback != null) { |
| _AsyncRun._scheduleImmediate(_startMicrotaskLoop); |
| } |
| } |
| } |
| |
| /// Schedules a callback to be called as a microtask. |
| /// |
| /// The microtask is called after all other currently scheduled |
| /// microtasks, but as part of the current system event. |
| void _scheduleAsyncCallback(_AsyncCallback callback) { |
| _AsyncCallbackEntry newEntry = new _AsyncCallbackEntry(callback); |
| _AsyncCallbackEntry? lastCallback = _lastCallback; |
| if (lastCallback == null) { |
| _nextCallback = _lastCallback = newEntry; |
| if (!_isInCallbackLoop) { |
| _AsyncRun._scheduleImmediate(_startMicrotaskLoop); |
| } |
| } else { |
| lastCallback.next = newEntry; |
| _lastCallback = newEntry; |
| } |
| } |
| |
| /// Schedules a callback to be called before all other currently scheduled ones. |
| /// |
| /// This callback takes priority over existing scheduled callbacks. |
| /// It is only used internally to give higher priority to error reporting. |
| /// |
| /// Is always run in the root zone. |
| void _schedulePriorityAsyncCallback(_AsyncCallback callback) { |
| if (_nextCallback == null) { |
| _scheduleAsyncCallback(callback); |
| _lastPriorityCallback = _lastCallback; |
| return; |
| } |
| _AsyncCallbackEntry entry = new _AsyncCallbackEntry(callback); |
| _AsyncCallbackEntry? lastPriorityCallback = _lastPriorityCallback; |
| if (lastPriorityCallback == null) { |
| entry.next = _nextCallback; |
| _nextCallback = _lastPriorityCallback = entry; |
| } else { |
| var next = lastPriorityCallback.next; |
| entry.next = next; |
| lastPriorityCallback.next = entry; |
| _lastPriorityCallback = entry; |
| if (next == null) { |
| _lastCallback = entry; |
| } |
| } |
| } |
| |
| /// Runs a function asynchronously. |
| /// |
| /// Callbacks registered through this function are always executed in order and |
| /// are guaranteed to run before other asynchronous events (like [Timer] events, |
| /// or DOM events). |
| /// |
| /// **Warning:** it is possible to starve the DOM by registering asynchronous |
| /// callbacks through this method. For example the following program runs |
| /// the callbacks without ever giving the Timer callback a chance to execute: |
| /// ```dart |
| /// main() { |
| /// Timer.run(() { print("executed"); }); // Will never be executed. |
| /// foo() { |
| /// scheduleMicrotask(foo); // Schedules [foo] in front of other events. |
| /// } |
| /// foo(); |
| /// } |
| /// ``` |
| /// ## Other resources |
| /// |
| /// * [The Event Loop and Dart](https://dart.dev/articles/event-loop/): |
| /// Learn how Dart handles the event queue and microtask queue, so you can write |
| /// better asynchronous code with fewer surprises. |
| @pragma('vm:entry-point', 'call') |
| void scheduleMicrotask(void Function() callback) { |
| _Zone currentZone = Zone._current; |
| if (identical(_rootZone, currentZone)) { |
| // No need to bind the callback. We know that the root's scheduleMicrotask |
| // will be invoked in the root zone. |
| _rootScheduleMicrotask(null, null, _rootZone, callback); |
| return; |
| } |
| _ZoneFunction implementation = currentZone._scheduleMicrotask; |
| if (identical(_rootZone, implementation.zone) && |
| _rootZone.inSameErrorZone(currentZone)) { |
| _rootScheduleMicrotask( |
| null, null, currentZone, currentZone.registerCallback(callback)); |
| return; |
| } |
| Zone.current.scheduleMicrotask(Zone.current.bindCallbackGuarded(callback)); |
| } |
| |
| class _AsyncRun { |
| /// Schedule the given callback before any other event in the event-loop. |
| external static void _scheduleImmediate(void Function() callback); |
| } |