Browse Source

Merge pull request #1810 from Kryptos-FR/feature/dispatcher-function

Support invoking a function in the dispatcher
pull/1824/head
Jeremy Koritzinsky 8 years ago
committed by GitHub
parent
commit
0702c5608a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      src/Avalonia.Base/Threading/Dispatcher.cs
  2. 11
      src/Avalonia.Base/Threading/IDispatcher.cs
  3. 168
      src/Avalonia.Base/Threading/JobRunner.cs
  4. 6
      tests/Avalonia.UnitTests/ImmediateDispatcher.cs

13
src/Avalonia.Base/Threading/Dispatcher.cs

@ -69,7 +69,7 @@ namespace Avalonia.Threading
/// </summary>
public void RunJobs()
{
_jobRunner?.RunJobs(null);
_jobRunner.RunJobs(null);
}
/// <summary>
@ -82,14 +82,21 @@ namespace Avalonia.Threading
public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
Contract.Requires<ArgumentNullException>(action != null);
return _jobRunner?.InvokeAsync(action, priority);
return _jobRunner.InvokeAsync(action, priority);
}
/// <inheritdoc/>
public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = DispatcherPriority.Normal)
{
Contract.Requires<ArgumentNullException>(function != null);
return _jobRunner.InvokeAsync(function, priority);
}
/// <inheritdoc/>
public void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
Contract.Requires<ArgumentNullException>(action != null);
_jobRunner?.Post(action, priority);
_jobRunner.Post(action, priority);
}
/// <summary>

11
src/Avalonia.Base/Threading/IDispatcher.cs

@ -28,12 +28,17 @@ namespace Avalonia.Threading
void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
/// <summary>
/// Post action that will be invoked on main thread
/// Posts an action that will be invoked on the dispatcher thread.
/// </summary>
/// <param name="action">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
// TODO: The naming of this method is confusing: the Async suffix usually means return a task.
// Remove this and rename InvokeTaskAsync as InvokeAsync. See #816.
Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
/// <summary>
/// Posts a function that will be invoked on the dispatcher thread.
/// </summary>
/// <param name="function">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = DispatcherPriority.Normal);
}
}

168
src/Avalonia.Base/Threading/JobRunner.cs

@ -14,32 +14,16 @@ namespace Avalonia.Threading
/// </summary>
internal class JobRunner
{
private IPlatformThreadingInterface _platform;
private Queue<Job>[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1)
.Select(_ => new Queue<Job>()).ToArray();
private readonly Queue<IJob>[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1)
.Select(_ => new Queue<IJob>()).ToArray();
public JobRunner(IPlatformThreadingInterface platform)
{
_platform = platform;
}
Job 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;
}
/// <summary>
/// Runs continuations pushed on the loop.
/// </summary>
@ -52,24 +36,8 @@ namespace Avalonia.Threading
var job = GetNextJob(minimumPriority);
if (job == null)
return;
if (job.TaskCompletionSource == null)
{
job.Action();
}
else
{
try
{
job.Action();
job.TaskCompletionSource.SetResult(null);
}
catch (Exception e)
{
job.TaskCompletionSource.SetException(e);
}
}
job.Run();
}
}
@ -83,7 +51,20 @@ namespace Avalonia.Threading
{
var job = new Job(action, priority, false);
AddJob(job);
return job.TaskCompletionSource.Task;
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 Job<TResult>(function, priority);
AddJob(job);
return job.Task;
}
/// <summary>
@ -105,9 +86,9 @@ namespace Avalonia.Threading
_platform = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>();
}
private void AddJob(Job job)
private void AddJob(IJob job)
{
var needWake = false;
bool needWake;
var queue = _queues[(int) job.Priority];
lock (queue)
{
@ -118,38 +99,129 @@ namespace Avalonia.Threading
_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
{
/// <summary>
/// Gets the job priority.
/// </summary>
DispatcherPriority Priority { get; }
/// <summary>
/// Runs the job.
/// </summary>
void Run();
}
/// <summary>
/// A job to run.
/// </summary>
private class Job
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 excepption in TaskCompletionSource</param>
/// <param name="throwOnUiThread">Do not wrap exception in TaskCompletionSource</param>
public Job(Action action, DispatcherPriority priority, bool throwOnUiThread)
{
Action = action;
_action = action;
Priority = priority;
TaskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource<object>();
_taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource<object>();
}
/// <inheritdoc/>
public DispatcherPriority Priority { get; }
/// <summary>
/// Gets the method to call.
/// The task.
/// </summary>
public Action Action { get; }
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 job to run.
/// </summary>
private sealed class Job<TResult> : IJob
{
private readonly Func<TResult> _function;
private readonly TaskCompletionSource<TResult> _taskCompletionSource;
/// <summary>
/// Gets the job priority.
/// Initializes a new instance of the <see cref="Job"/> class.
/// </summary>
public DispatcherPriority Priority { get; }
/// <param name="function">The method to call.</param>
/// <param name="priority">The job priority.</param>
public Job(Func<TResult> function, DispatcherPriority priority)
{
_function = function;
Priority = priority;
_taskCompletionSource = new TaskCompletionSource<TResult>();
}
/// <inheritdoc/>
public DispatcherPriority Priority { get; }
/// <summary>
/// Gets the task completion source.
/// The task.
/// </summary>
public TaskCompletionSource<object> TaskCompletionSource { get; }
public Task<TResult> Task => _taskCompletionSource.Task;
/// <inheritdoc/>
void IJob.Run()
{
try
{
var result = _function();
_taskCompletionSource.SetResult(result);
}
catch (Exception e)
{
_taskCompletionSource.SetException(e);
}
}
}
}
}

6
tests/Avalonia.UnitTests/ImmediateDispatcher.cs

@ -25,6 +25,12 @@ namespace Avalonia.UnitTests
return Task.FromResult<object>(null);
}
public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = DispatcherPriority.Normal)
{
var result = function();
return Task.FromResult(result);
}
public void VerifyAccess()
{
}

Loading…
Cancel
Save