// 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