|
|
@@ -9,6 +9,16 @@ namespace Avalonia.Threading
|
|
|
/// </summary>
|
|
|
public class AvaloniaScheduler : LocalScheduler
|
|
|
{
|
|
|
+ /// <summary>
|
|
|
+ /// 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 <see cref="Schedule{TState}"/> before we will
|
|
|
+ /// schedule on a dispatcher anyway.
|
|
|
+ /// </summary>
|
|
|
+ private const int MaxReentrantSchedules = 32;
|
|
|
+
|
|
|
+ private int _reentrancyGuard;
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// The instance of the <see cref="AvaloniaScheduler"/>.
|
|
|
/// </summary>
|
|
|
@@ -24,31 +34,58 @@ namespace Avalonia.Threading
|
|
|
/// <inheritdoc/>
|
|
|
public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> 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;
|
|
|
}
|
|
|
}
|
|
|
}
|