Description
RichTextBox starts its caret blink in the constructor path (InitializeAdornerElements → InitializeBlinkAnimation) with:
- an
Animation whose IterationCount = IterationCount.Infinite, and
blinkAnimation.RunAsync(_CaretRect); — fire-and-forget, the returned Task is neither awaited nor stored.
Avalonia refuses infinite animations on the Run/RunAsync path: instead of throwing, it returns a faulted task — Task.FromException(new InvalidOperationException("Looping animations must not use the Run method.")).
Consequences
- The caret never actually blinks (the animation never starts).
- Every
new RichTextBox() produces a faulted, unobserved Task that later surfaces as TaskScheduler.UnobservedTaskException on the finalizer thread — with an empty stack trace (the exception is never thrown), which makes it very hard for consumers to trace back to this control.
- Consumers cannot observe or prevent it: the field is private and the task is discarded inside the constructor.
In our app (digital signage kiosk that periodically rebuilds screens containing several RichTextBox-based widgets) this generated ~940 telemetry events over 2 weeks across 4 devices before we could locate the source.
Repro
TaskScheduler.UnobservedTaskException += (s, e) => Console.WriteLine(e.Exception);
var rtb = new AvRichTextBox.RichTextBox(); // creates the faulted task
rtb = null;
GC.Collect(); GC.WaitForPendingFinalizers();
// -> AggregateException(InvalidOperationException("Looping animations must not use the Run method."))
Package: Simplecto.Avalonia.RichTextBox 1.9.3 (latest) · Avalonia 12.x (the RunAsync behavior is the same on 11.x). Still present on current main.
Suggested fix
Any of:
- drive the blink through the styling/
Apply path (which supports IterationCount.Infinite), or
- run a finite animation restarted on completion, or
- keep
RunAsync but with a finite iteration count and observe the returned task.
Ideally only start blinking when the control actually has focus / the caret is visible — that would also avoid the cost for read-only usages.
Workaround (downstream)
We filter this exact message in our global UnobservedTaskException handler, which silences the noise but obviously doesn't restore the blink.
Thanks for the control — happy to test a fix.
Description
RichTextBoxstarts its caret blink in the constructor path (InitializeAdornerElements→InitializeBlinkAnimation) with:AnimationwhoseIterationCount = IterationCount.Infinite, andblinkAnimation.RunAsync(_CaretRect);— fire-and-forget, the returnedTaskis neither awaited nor stored.Avalonia refuses infinite animations on the
Run/RunAsyncpath: instead of throwing, it returns a faulted task —Task.FromException(new InvalidOperationException("Looping animations must not use the Run method.")).Consequences
new RichTextBox()produces a faulted, unobserved Task that later surfaces asTaskScheduler.UnobservedTaskExceptionon the finalizer thread — with an empty stack trace (the exception is never thrown), which makes it very hard for consumers to trace back to this control.In our app (digital signage kiosk that periodically rebuilds screens containing several RichTextBox-based widgets) this generated ~940 telemetry events over 2 weeks across 4 devices before we could locate the source.
Repro
Package:
Simplecto.Avalonia.RichTextBox1.9.3 (latest) · Avalonia 12.x (theRunAsyncbehavior is the same on 11.x). Still present on currentmain.Suggested fix
Any of:
Applypath (which supportsIterationCount.Infinite), orRunAsyncbut with a finite iteration count and observe the returned task.Ideally only start blinking when the control actually has focus / the caret is visible — that would also avoid the cost for read-only usages.
Workaround (downstream)
We filter this exact message in our global
UnobservedTaskExceptionhandler, which silences the noise but obviously doesn't restore the blink.Thanks for the control — happy to test a fix.