9 changed files with 151 additions and 395 deletions
@ -1,300 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Threading |
|||
{ |
|||
/// <summary>
|
|||
/// A main loop in a <see cref="Dispatcher"/>.
|
|||
/// </summary>
|
|||
internal class JobRunner |
|||
{ |
|||
private IPlatformThreadingInterface? _platform; |
|||
|
|||
private readonly Queue<IJob>[] _queues = Enumerable.Range(0, (int)DispatcherPriority.MaxValue + 1) |
|||
.Select(_ => new Queue<IJob>()).ToArray(); |
|||
|
|||
public JobRunner(IPlatformThreadingInterface? platform) |
|||
{ |
|||
_platform = platform; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Runs continuations pushed on the loop.
|
|||
/// </summary>
|
|||
/// <param name="priority">Priority to execute jobs for. Pass null if platform doesn't have internal priority system</param>
|
|||
public void RunJobs(DispatcherPriority? priority) |
|||
{ |
|||
var minimumPriority = priority ?? DispatcherPriority.MinimumActiveValue; |
|||
while (true) |
|||
{ |
|||
var job = GetNextJob(minimumPriority); |
|||
if (job == null) |
|||
return; |
|||
|
|||
job.Run(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invokes a method on the main loop.
|
|||
/// </summary>
|
|||
/// <param name="action">The method.</param>
|
|||
/// <param name="priority">The priority with which to invoke the method.</param>
|
|||
/// <returns>A task that can be used to track the method's execution.</returns>
|
|||
public Task InvokeAsync(Action action, DispatcherPriority priority) |
|||
{ |
|||
var job = new Job(action, priority, false); |
|||
AddJob(job); |
|||
return job.Task!; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invokes a method on the main loop.
|
|||
/// </summary>
|
|||
/// <param name="function">The method.</param>
|
|||
/// <param name="priority">The priority with which to invoke the method.</param>
|
|||
/// <returns>A task that can be used to track the method's execution.</returns>
|
|||
public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority) |
|||
{ |
|||
var job = new JobWithResult<TResult>(function, priority); |
|||
AddJob(job); |
|||
return job.Task; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Post action that will be invoked on main thread
|
|||
/// </summary>
|
|||
/// <param name="action">The method.</param>
|
|||
///
|
|||
/// <param name="priority">The priority with which to invoke the method.</param>
|
|||
internal void Post(Action action, DispatcherPriority priority) |
|||
{ |
|||
AddJob(new Job(action, priority, true)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Post action that will be invoked on main thread
|
|||
/// </summary>
|
|||
/// <param name="action">The method to call.</param>
|
|||
/// <param name="parameter">The parameter of method to call.</param>
|
|||
/// <param name="priority">The priority with which to invoke the method.</param>
|
|||
internal void Post(SendOrPostCallback action, object? parameter, DispatcherPriority priority) |
|||
{ |
|||
AddJob(new JobWithArg(action, parameter, priority, true)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Allows unit tests to change the platform threading interface.
|
|||
/// </summary>
|
|||
internal void UpdateServices() |
|||
{ |
|||
_platform = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>(); |
|||
} |
|||
|
|||
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; |
|||
} |
|||
|
|||
public bool HasJobsWithPriority(DispatcherPriority minimumPriority) |
|||
{ |
|||
for (int c = (int)minimumPriority; c < (int)DispatcherPriority.MaxValue; c++) |
|||
{ |
|||
var q = _queues[c]; |
|||
lock (q) |
|||
{ |
|||
if (q.Count > 0) |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
private interface IJob |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the job priority.
|
|||
/// </summary>
|
|||
DispatcherPriority Priority { get; } |
|||
|
|||
/// <summary>
|
|||
/// Runs the job.
|
|||
/// </summary>
|
|||
void Run(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A job to run.
|
|||
/// </summary>
|
|||
private sealed class Job : IJob |
|||
{ |
|||
/// <summary>
|
|||
/// The method to call.
|
|||
/// </summary>
|
|||
private readonly Action _action; |
|||
/// <summary>
|
|||
/// The task completion source.
|
|||
/// </summary>
|
|||
private readonly TaskCompletionSource<object?>? _taskCompletionSource; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Job"/> class.
|
|||
/// </summary>
|
|||
/// <param name="action">The method to call.</param>
|
|||
/// <param name="priority">The job priority.</param>
|
|||
/// <param name="throwOnUiThread">Do not wrap exception in TaskCompletionSource</param>
|
|||
public Job(Action action, DispatcherPriority priority, bool throwOnUiThread) |
|||
{ |
|||
_action = action; |
|||
Priority = priority; |
|||
_taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource<object?>(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public DispatcherPriority Priority { get; } |
|||
|
|||
/// <summary>
|
|||
/// The task.
|
|||
/// </summary>
|
|||
public Task? Task => _taskCompletionSource?.Task; |
|||
|
|||
/// <inheritdoc/>
|
|||
void IJob.Run() |
|||
{ |
|||
if (_taskCompletionSource == null) |
|||
{ |
|||
_action(); |
|||
return; |
|||
} |
|||
try |
|||
{ |
|||
_action(); |
|||
_taskCompletionSource.SetResult(null); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
_taskCompletionSource.SetException(e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A typed job to run.
|
|||
/// </summary>
|
|||
private sealed class JobWithArg : IJob |
|||
{ |
|||
private readonly SendOrPostCallback _action; |
|||
private readonly object? _parameter; |
|||
private readonly TaskCompletionSource<bool>? _taskCompletionSource; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Job"/> class.
|
|||
/// </summary>
|
|||
/// <param name="action">The method to call.</param>
|
|||
/// <param name="parameter">The parameter of method to call.</param>
|
|||
/// <param name="priority">The job priority.</param>
|
|||
/// <param name="throwOnUiThread">Do not wrap exception in TaskCompletionSource</param>
|
|||
|
|||
public JobWithArg(SendOrPostCallback action, object? parameter, DispatcherPriority priority, bool throwOnUiThread) |
|||
{ |
|||
_action = action; |
|||
_parameter = parameter; |
|||
Priority = priority; |
|||
_taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource<bool>(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public DispatcherPriority Priority { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
void IJob.Run() |
|||
{ |
|||
if (_taskCompletionSource == null) |
|||
{ |
|||
_action(_parameter); |
|||
return; |
|||
} |
|||
try |
|||
{ |
|||
_action(_parameter); |
|||
_taskCompletionSource.SetResult(default); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
_taskCompletionSource.SetException(e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A job to run thath return value.
|
|||
/// </summary>
|
|||
/// <typeparam name="TResult">Type of job result</typeparam>
|
|||
private sealed class JobWithResult<TResult> : IJob |
|||
{ |
|||
private readonly Func<TResult> _function; |
|||
private readonly TaskCompletionSource<TResult> _taskCompletionSource; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Job"/> class.
|
|||
/// </summary>
|
|||
/// <param name="function">The method to call.</param>
|
|||
/// <param name="priority">The job priority.</param>
|
|||
public JobWithResult(Func<TResult> function, DispatcherPriority priority) |
|||
{ |
|||
_function = function; |
|||
Priority = priority; |
|||
_taskCompletionSource = new TaskCompletionSource<TResult>(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public DispatcherPriority Priority { get; } |
|||
|
|||
/// <summary>
|
|||
/// The task.
|
|||
/// </summary>
|
|||
public Task<TResult> Task => _taskCompletionSource.Task; |
|||
|
|||
/// <inheritdoc/>
|
|||
void IJob.Run() |
|||
{ |
|||
try |
|||
{ |
|||
var result = _function(); |
|||
_taskCompletionSource.SetResult(result); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
_taskCompletionSource.SetException(e); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Utilities; |
|||
|
|||
public class DispatcherTimerHelper |
|||
{ |
|||
|
|||
} |
|||
|
|||
public static class DispatcherTimerUtils |
|||
{ |
|||
public static void ForceFire(this DispatcherTimer timer) |
|||
{ |
|||
timer.Promote(); |
|||
timer.Dispatcher.RunJobs(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue