136 changed files with 2858 additions and 2050 deletions
@ -0,0 +1,216 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Threading; |
|||
|
|||
public partial class Dispatcher |
|||
{ |
|||
internal bool ExitAllFramesRequested { get; private set; } |
|||
internal bool HasShutdownStarted { get; private set; } |
|||
internal int DisabledProcessingCount { get; set; } |
|||
private bool _hasShutdownFinished; |
|||
private bool _startingShutdown; |
|||
|
|||
private Stack<DispatcherFrame> _frames = new(); |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Raised when the dispatcher is shutting down.
|
|||
/// </summary>
|
|||
public event EventHandler? ShutdownStarted; |
|||
|
|||
/// <summary>
|
|||
/// Raised when the dispatcher is shut down.
|
|||
/// </summary>
|
|||
public event EventHandler? ShutdownFinished; |
|||
|
|||
/// <summary>
|
|||
/// Push an execution frame.
|
|||
/// </summary>
|
|||
/// <param name="frame">
|
|||
/// The frame for the dispatcher to process.
|
|||
/// </param>
|
|||
public void PushFrame(DispatcherFrame frame) |
|||
{ |
|||
VerifyAccess(); |
|||
if (_controlledImpl == null) |
|||
throw new PlatformNotSupportedException(); |
|||
_ = frame ?? throw new ArgumentNullException(nameof(frame)); |
|||
|
|||
if(_hasShutdownFinished) // Dispatcher thread - no lock needed for read
|
|||
throw new InvalidOperationException("Cannot perform requested operation because the Dispatcher shut down"); |
|||
|
|||
if (DisabledProcessingCount > 0) |
|||
throw new InvalidOperationException( |
|||
"Cannot perform this operation while dispatcher processing is suspended."); |
|||
|
|||
try |
|||
{ |
|||
_frames.Push(frame); |
|||
using (AvaloniaSynchronizationContext.Ensure(DispatcherPriority.Normal)) |
|||
frame.Run(_controlledImpl); |
|||
} |
|||
finally |
|||
{ |
|||
_frames.Pop(); |
|||
if (_frames.Count == 0) |
|||
{ |
|||
if (HasShutdownStarted) |
|||
ShutdownImpl(); |
|||
else |
|||
ExitAllFramesRequested = false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Runs the dispatcher's main loop.
|
|||
/// </summary>
|
|||
/// <param name="cancellationToken">
|
|||
/// A cancellation token used to exit the main loop.
|
|||
/// </param>
|
|||
public void MainLoop(CancellationToken cancellationToken) |
|||
{ |
|||
if (_controlledImpl == null) |
|||
throw new PlatformNotSupportedException(); |
|||
var frame = new DispatcherFrame(); |
|||
cancellationToken.Register(() => frame.Continue = false); |
|||
PushFrame(frame); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Requests that all nested frames exit.
|
|||
/// </summary>
|
|||
public void ExitAllFrames() |
|||
{ |
|||
if (_frames.Count == 0) |
|||
return; |
|||
ExitAllFramesRequested = true; |
|||
foreach (var f in _frames) |
|||
f.MaybeExitOnDispatcherRequest(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Begins the process of shutting down the dispatcher.
|
|||
/// </summary>
|
|||
public void BeginInvokeShutdown(DispatcherPriority priority) => Post(StartShutdownImpl, priority); |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Initiates the shutdown process of the Dispatcher synchronously.
|
|||
/// </summary>
|
|||
public void InvokeShutdown() => Invoke(StartShutdownImpl, DispatcherPriority.Send); |
|||
|
|||
private void StartShutdownImpl() |
|||
{ |
|||
if (!_startingShutdown) |
|||
{ |
|||
// We only need this to prevent reentrancy if the ShutdownStarted event
|
|||
// tries to shut down again.
|
|||
_startingShutdown = true; |
|||
|
|||
// Call the ShutdownStarted event before we actually mark ourselves
|
|||
// as shutting down. This is so the handlers can actually do work
|
|||
// when they get this event without throwing exceptions.
|
|||
ShutdownStarted?.Invoke(this, EventArgs.Empty); |
|||
|
|||
HasShutdownStarted = true; |
|||
|
|||
if (_frames.Count > 0) |
|||
ExitAllFrames(); |
|||
else ShutdownImpl(); |
|||
} |
|||
} |
|||
|
|||
|
|||
private void ShutdownImpl() |
|||
{ |
|||
DispatcherOperation? operation = null; |
|||
_impl.Timer -= PromoteTimers; |
|||
_impl.Signaled -= Signaled; |
|||
do |
|||
{ |
|||
lock (InstanceLock) |
|||
{ |
|||
if (_queue.MaxPriority != DispatcherPriority.Invalid) |
|||
{ |
|||
operation = _queue.Peek(); |
|||
} |
|||
else |
|||
{ |
|||
operation = null; |
|||
} |
|||
} |
|||
|
|||
if (operation != null) |
|||
{ |
|||
operation.Abort(); |
|||
} |
|||
} while (operation != null); |
|||
|
|||
_impl.UpdateTimer(null); |
|||
_hasShutdownFinished = true; |
|||
ShutdownFinished?.Invoke(this, EventArgs.Empty); |
|||
} |
|||
|
|||
public record struct DispatcherProcessingDisabled : IDisposable |
|||
{ |
|||
private readonly SynchronizationContext? _oldContext; |
|||
|
|||
private readonly bool _restoreContext; |
|||
private Dispatcher? _dispatcher; |
|||
|
|||
internal DispatcherProcessingDisabled(Dispatcher dispatcher) |
|||
{ |
|||
_dispatcher = dispatcher; |
|||
} |
|||
|
|||
internal DispatcherProcessingDisabled(Dispatcher dispatcher, SynchronizationContext? oldContext) : this( |
|||
dispatcher) |
|||
{ |
|||
_oldContext = oldContext; |
|||
_restoreContext = true; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if(_dispatcher==null) |
|||
return; |
|||
_dispatcher.DisabledProcessingCount--; |
|||
_dispatcher = null; |
|||
if (_restoreContext) |
|||
SynchronizationContext.SetSynchronizationContext(_oldContext); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Disable the event processing of the dispatcher.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This is an advanced method intended to eliminate the chance of
|
|||
/// unrelated reentrancy. The effect of disabling processing is:
|
|||
/// 1) CLR locks will not pump messages internally.
|
|||
/// 2) No one is allowed to push a frame.
|
|||
/// 3) No message processing is permitted.
|
|||
/// </remarks>
|
|||
public DispatcherProcessingDisabled DisableProcessing() |
|||
{ |
|||
VerifyAccess(); |
|||
|
|||
// Turn off processing.
|
|||
DisabledProcessingCount++; |
|||
var oldContext = SynchronizationContext.Current; |
|||
if (oldContext is AvaloniaSynchronizationContext or NonPumpingSyncContext) |
|||
return new DispatcherProcessingDisabled(this); |
|||
|
|||
var helper = AvaloniaLocator.Current.GetService<NonPumpingLockHelper.IHelperImpl>(); |
|||
if (helper == null) |
|||
return new DispatcherProcessingDisabled(this); |
|||
|
|||
SynchronizationContext.SetSynchronizationContext(new NonPumpingSyncContext(helper, oldContext)); |
|||
return new DispatcherProcessingDisabled(this, oldContext); |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,125 @@ |
|||
using System; |
|||
using System.Threading; |
|||
|
|||
namespace Avalonia.Threading; |
|||
|
|||
/// <summary>
|
|||
/// Representation of Dispatcher frame.
|
|||
/// </summary>
|
|||
public class DispatcherFrame |
|||
{ |
|||
private bool _exitWhenRequested; |
|||
private bool _continue; |
|||
private bool _isRunning; |
|||
private CancellationTokenSource? _cancellationTokenSource; |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new instance of the DispatcherFrame class.
|
|||
/// </summary>
|
|||
public DispatcherFrame() : this(true) |
|||
{ |
|||
} |
|||
|
|||
public Dispatcher Dispatcher { get; } |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new instance of the DispatcherFrame class.
|
|||
/// </summary>
|
|||
/// <param name="exitWhenRequested">
|
|||
/// Indicates whether or not this frame will exit when all frames
|
|||
/// are requested to exit.
|
|||
/// <p/>
|
|||
/// Dispatcher frames typically break down into two categories:
|
|||
/// 1) Long running, general purpose frames, that exit only when
|
|||
/// told to. These frames should exit when requested.
|
|||
/// 2) Short running, very specific frames that exit themselves
|
|||
/// when an important criteria is met. These frames may
|
|||
/// consider not exiting when requested in favor of waiting
|
|||
/// for their important criteria to be met. These frames
|
|||
/// should have a timeout associated with them.
|
|||
/// </param>
|
|||
public DispatcherFrame(bool exitWhenRequested) |
|||
{ |
|||
Dispatcher = Dispatcher.UIThread; |
|||
Dispatcher.VerifyAccess(); |
|||
_exitWhenRequested = exitWhenRequested; |
|||
_continue = true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Indicates that this dispatcher frame should exit.
|
|||
/// </summary>
|
|||
public bool Continue |
|||
{ |
|||
get |
|||
{ |
|||
// This method is free-threaded.
|
|||
|
|||
// First check if this frame wants to continue.
|
|||
bool shouldContinue = _continue; |
|||
if (shouldContinue) |
|||
{ |
|||
// This frame wants to continue, so next check if it will
|
|||
// respect the "exit requests" from the dispatcher.
|
|||
if (_exitWhenRequested) |
|||
{ |
|||
Dispatcher dispatcher = Dispatcher; |
|||
|
|||
// This frame is willing to respect the "exit requests" of
|
|||
// the dispatcher, so check them.
|
|||
if (dispatcher.ExitAllFramesRequested || dispatcher.HasShutdownStarted) |
|||
{ |
|||
shouldContinue = false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return shouldContinue; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
// This method is free-threaded.
|
|||
lock (Dispatcher.InstanceLock) |
|||
{ |
|||
_continue = value; |
|||
if (!_continue) |
|||
_cancellationTokenSource?.Cancel(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
internal void Run(IControlledDispatcherImpl impl) |
|||
{ |
|||
// Since the actual platform run loop is controlled by a Cancellation token, we are restarting
|
|||
// it if frame still needs to run
|
|||
while (Continue) |
|||
RunCore(impl); |
|||
} |
|||
|
|||
private void RunCore(IControlledDispatcherImpl impl) |
|||
{ |
|||
if (_isRunning) |
|||
throw new InvalidOperationException("This frame is already running"); |
|||
_isRunning = true; |
|||
try |
|||
{ |
|||
_cancellationTokenSource = new CancellationTokenSource(); |
|||
// Wake up the dispatcher in case it has pending jobs
|
|||
Dispatcher.RequestProcessing(); |
|||
impl.RunLoop(_cancellationTokenSource.Token); |
|||
} |
|||
finally |
|||
{ |
|||
_isRunning = false; |
|||
_cancellationTokenSource?.Cancel(); |
|||
_cancellationTokenSource = null; |
|||
} |
|||
} |
|||
|
|||
internal void MaybeExitOnDispatcherRequest() |
|||
{ |
|||
if (_exitWhenRequested) |
|||
_cancellationTokenSource?.Cancel(); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the position of a color's alpha component relative to all other components.
|
|||
/// </summary>
|
|||
public enum AlphaComponentPosition |
|||
{ |
|||
/// <summary>
|
|||
/// The alpha component occurs before all other components.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// For example, this may indicate the #AARRGGBB or ARGB format which
|
|||
/// is the default format for XAML itself and the Color struct.
|
|||
/// </remarks>
|
|||
Leading, |
|||
|
|||
/// <summary>
|
|||
/// The alpha component occurs after all other components.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// For example, this may indicate the #RRGGBBAA or RGBA format which
|
|||
/// is the default format for CSS.
|
|||
/// </remarks>
|
|||
Trailing, |
|||
} |
|||
} |
|||
@ -1,24 +1,24 @@ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Globalization; |
|||
|
|||
using Avalonia.Media; |
|||
|
|||
|
|||
namespace Avalonia.Markup.Xaml.Converters |
|||
{ |
|||
public class FontFamilyTypeConverter : TypeConverter |
|||
{ |
|||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) |
|||
/// <inheritdoc />
|
|||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) |
|||
{ |
|||
return sourceType == typeof(string); |
|||
} |
|||
|
|||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) |
|||
/// <inheritdoc />
|
|||
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) |
|||
{ |
|||
var s = (string)value; |
|||
|
|||
return FontFamily.Parse(s, context.GetContextBaseUri()); |
|||
return FontFamily.Parse(s, context?.GetContextBaseUri()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -1,33 +1,31 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Text; |
|||
using Avalonia.Data.Core.Plugins; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings |
|||
{ |
|||
class ObservableStreamPlugin<T> : IStreamPlugin |
|||
internal class ObservableStreamPlugin<T> : IStreamPlugin |
|||
{ |
|||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|||
public bool Match(WeakReference<object> reference) |
|||
public bool Match(WeakReference<object?> reference) |
|||
{ |
|||
return reference.TryGetTarget(out var target) && target is IObservable<T>; |
|||
} |
|||
|
|||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|||
public IObservable<object> Start(WeakReference<object> reference) |
|||
public IObservable<object?> Start(WeakReference<object?> reference) |
|||
{ |
|||
if (!(reference.TryGetTarget(out var target) && target is IObservable<T> obs)) |
|||
{ |
|||
return Observable.Empty<object>(); |
|||
return Observable.Empty<object?>(); |
|||
} |
|||
else if (target is IObservable<object> obj) |
|||
else if (target is IObservable<object?> obj) |
|||
{ |
|||
return obj; |
|||
} |
|||
|
|||
return obs.Select(x => (object)x); |
|||
return obs.Select(x => (object?)x); |
|||
} |
|||
} |
|||
} |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue