diff --git a/src/Avalonia.Base/Threading/AvaloniaScheduler.cs b/src/Avalonia.Base/Threading/AvaloniaScheduler.cs
index 0bfc713ba0..397826df53 100644
--- a/src/Avalonia.Base/Threading/AvaloniaScheduler.cs
+++ b/src/Avalonia.Base/Threading/AvaloniaScheduler.cs
@@ -9,6 +9,16 @@ namespace Avalonia.Threading
///
public class AvaloniaScheduler : LocalScheduler
{
+ ///
+ /// Users can schedule actions on the dispatcher thread while being on the correct thread already.
+ /// We are optimizing this case by invoking user callback immediately which can lead to stack overflows in certain cases.
+ /// To prevent this we are limiting amount of reentrant calls to before we will
+ /// schedule on a dispatcher anyway.
+ ///
+ private const int MaxReentrantSchedules = 32;
+
+ private int _reentrancyGuard;
+
///
/// The instance of the .
///
@@ -24,31 +34,58 @@ namespace Avalonia.Threading
///
public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action)
{
- var composite = new CompositeDisposable(2);
+ IDisposable PostOnDispatcher()
+ {
+ var composite = new CompositeDisposable(2);
+
+ var cancellation = new CancellationDisposable();
+
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (!cancellation.Token.IsCancellationRequested)
+ {
+ composite.Add(action(this, state));
+ }
+ }, DispatcherPriority.DataBind);
+
+ composite.Add(cancellation);
+
+ return composite;
+ }
+
if (dueTime == TimeSpan.Zero)
{
if (!Dispatcher.UIThread.CheckAccess())
{
- var cancellation = new CancellationDisposable();
- Dispatcher.UIThread.Post(() =>
- {
- if (!cancellation.Token.IsCancellationRequested)
- {
- composite.Add(action(this, state));
- }
- }, DispatcherPriority.DataBind);
- composite.Add(cancellation);
+ return PostOnDispatcher();
}
else
{
- return action(this, state);
+ if (_reentrancyGuard >= MaxReentrantSchedules)
+ {
+ return PostOnDispatcher();
+ }
+
+ try
+ {
+ _reentrancyGuard++;
+
+ return action(this, state);
+ }
+ finally
+ {
+ _reentrancyGuard--;
+ }
}
}
else
{
+ var composite = new CompositeDisposable(2);
+
composite.Add(DispatcherTimer.RunOnce(() => composite.Add(action(this, state)), dueTime));
+
+ return composite;
}
- return composite;
}
}
}