// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Avalonia.Platform; namespace Avalonia.Threading { /// /// A main loop in a . /// internal class JobRunner { private IPlatformThreadingInterface _platform; private readonly Queue[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1) .Select(_ => new Queue()).ToArray(); public JobRunner(IPlatformThreadingInterface platform) { _platform = platform; } /// /// Runs continuations pushed on the loop. /// /// Priority to execute jobs for. Pass null if platform doesn't have internal priority system public void RunJobs(DispatcherPriority? priority) { var minimumPriority = priority ?? DispatcherPriority.MinValue; while (true) { var job = GetNextJob(minimumPriority); if (job == null) return; job.Run(); } } /// /// Invokes a method on the main loop. /// /// The method. /// The priority with which to invoke the method. /// A task that can be used to track the method's execution. public Task InvokeAsync(Action action, DispatcherPriority priority) { var job = new Job(action, priority, false); AddJob(job); return job.Task; } /// /// Invokes a method on the main loop. /// /// The method. /// The priority with which to invoke the method. /// A task that can be used to track the method's execution. public Task InvokeAsync(Func function, DispatcherPriority priority) { var job = new Job(function, priority); AddJob(job); return job.Task; } /// /// Post action that will be invoked on main thread /// /// The method. /// /// The priority with which to invoke the method. internal void Post(Action action, DispatcherPriority priority) { AddJob(new Job(action, priority, true)); } /// /// Allows unit tests to change the platform threading interface. /// internal void UpdateServices() { _platform = AvaloniaLocator.Current.GetService(); } private void AddJob(IJob job) { bool needWake; var queue = _queues[(int) job.Priority]; lock (queue) { needWake = queue.Count == 0; queue.Enqueue(job); } if (needWake) _platform?.Signal(job.Priority); } private IJob GetNextJob(DispatcherPriority minimumPriority) { for (int c = (int) DispatcherPriority.MaxValue; c >= (int) minimumPriority; c--) { var q = _queues[c]; lock (q) { if (q.Count > 0) return q.Dequeue(); } } return null; } private interface IJob { /// /// Gets the job priority. /// DispatcherPriority Priority { get; } /// /// Runs the job. /// void Run(); } /// /// A job to run. /// private sealed class Job : IJob { /// /// The method to call. /// private readonly Action _action; /// /// The task completion source. /// private readonly TaskCompletionSource _taskCompletionSource; /// /// Initializes a new instance of the class. /// /// The method to call. /// The job priority. /// Do not wrap exception in TaskCompletionSource public Job(Action action, DispatcherPriority priority, bool throwOnUiThread) { _action = action; Priority = priority; _taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource(); } /// public DispatcherPriority Priority { get; } /// /// The task. /// public Task Task => _taskCompletionSource?.Task; /// void IJob.Run() { if (_taskCompletionSource == null) { _action(); return; } try { _action(); _taskCompletionSource.SetResult(null); } catch (Exception e) { _taskCompletionSource.SetException(e); } } } /// /// A job to run. /// private sealed class Job : IJob { private readonly Func _function; private readonly TaskCompletionSource _taskCompletionSource; /// /// Initializes a new instance of the class. /// /// The method to call. /// The job priority. public Job(Func function, DispatcherPriority priority) { _function = function; Priority = priority; _taskCompletionSource = new TaskCompletionSource(); } /// public DispatcherPriority Priority { get; } /// /// The task. /// public Task Task => _taskCompletionSource.Task; /// void IJob.Run() { try { var result = _function(); _taskCompletionSource.SetResult(result); } catch (Exception e) { _taskCompletionSource.SetException(e); } } } } }