Repository: AvaloniaUI/Avalonia
File: src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs (C#)

Code before:
            }
        }

        private void StartInertia(TimeSpan timeSpan)
        {
            if (_inertia is { } inertia)
            {
                _lastTime = timeSpan;
                _inertiaStartTime = timeSpan;
                _currentInertiaGestureId = _gestureId;
                Target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, inertia));

                MediaContext.Instance.RequestAnimationFrame(OnAnimationRequested);
            }
        }

        private void OnAnimationRequested(TimeSpan timeSpan)
        {
            // Another gesture has started, finish the current one
            if (_gestureId != _currentInertiaGestureId || _inertia == null)
            {
                _inertia = null;
                return;
            }

            var elapsedSinceLastTick = timeSpan - _lastTime;
            _lastTime = timeSpan;

            var speed = _inertia * Math.Pow(InertialResistance, (_lastTime - _inertiaStartTime).TotalSeconds);
            var distance = speed * elapsedSinceLastTick.TotalSeconds;
            _inertiaDispatchOperation = Dispatcher.UIThread.InvokeAsync(() =>
            {
                if (speed is not { } && distance is not { })
                {
                    EndGesture();
                    return;
                }
                var scrollGestureEventArgs = new ScrollGestureEventArgs(_gestureId, distance!.Value);
                Target!.RaiseEvent(scrollGestureEventArgs);

                if (!scrollGestureEventArgs.Handled || scrollGestureEventArgs.ShouldEndScrollGesture)
                {
                    EndGesture();
                    return;
                }

                var sp = speed!.Value;

                // EndGesture using InertialScrollSpeedEnd only in the direction of scrolling
                if (CanVerticallyScroll && CanHorizontallyScroll && Math.Abs(sp.X) < InertialScrollSpeedEnd && Math.Abs(sp.Y) <= InertialScrollSpeedEnd)
                {

Review comment (suggestion):
All of those calculations should live in `Input` priority callback. The **only** job of animation frame callbacks is to throttle inertia events. `OnAnimationRequested` should just post a callback via dispatcher.

Code after:
                    _currentInertiaGestureId = _gestureId;
                    Target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia.Value));
                    MediaContext.Instance.RequestAnimationFrame(OnAnimationRequested);
                }
            }
        }

        private void OnAnimationRequested(TimeSpan _)
        {
            // Calculate the current speed and dispatch the next inertia event. This is done asynchronously so we have run the events
            // with Input priority
            Dispatcher.UIThread.InvokeAsync(() =>
            {
                // Another gesture has started, finish the current one
                if (_gestureId != _currentInertiaGestureId || _stopWatch == null || _inertia is not Vector inertia)
                {
                    return;
                }

                var timeSpan = _stopWatch.Elapsed;
                var elapsedSinceLastTick = timeSpan - _lastTime;
                _lastTime = timeSpan;

                var speed = inertia * Math.Pow(InertialResistance, (_lastTime - _inertiaStartTime).TotalSeconds);
                var distance = speed * elapsedSinceLastTick.TotalSeconds;
                var scrollGestureEventArgs = new ScrollGestureEventArgs(_gestureId, distance);
                Target!.RaiseEvent(scrollGestureEventArgs);

                if (!scrollGestureEventArgs.Handled || scrollGestureEventArgs.ShouldEndScrollGesture)
                {
                    EndGesture();
                    return;
                }

                // EndGesture using InertialScrollSpeedEnd only in the direction of scrolling
                if (CanVerticallyScroll && CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd && Math.Abs(speed.Y) <= InertialScrollSpeedEnd)
                {
                    // NO-OP 
                }
                else if (CanVerticallyScroll && Math.Abs(speed.Y) <= InertialScrollSpeedEnd)
                {
                    EndGesture();
                    return;
                }
                else if (CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd)
                {
                    EndGesture();
                    return;
                }

                // Reschedule on the next animation frame. TopLevel.RequestAnimationFrame isn't available on the Base project, so we use the global MediaContext