diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index cf7acb3e8a..aa2a7a7a8e 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -69,7 +69,7 @@ namespace Avalonia.Threading /// public void RunJobs() { - _jobRunner?.RunJobs(null); + _jobRunner.RunJobs(null); } /// @@ -82,14 +82,21 @@ namespace Avalonia.Threading public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { Contract.Requires(action != null); - return _jobRunner?.InvokeAsync(action, priority); + return _jobRunner.InvokeAsync(action, priority); + } + + /// + public Task InvokeAsync(Func function, DispatcherPriority priority = DispatcherPriority.Normal) + { + Contract.Requires(function != null); + return _jobRunner.InvokeAsync(function, priority); } /// public void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { Contract.Requires(action != null); - _jobRunner?.Post(action, priority); + _jobRunner.Post(action, priority); } /// diff --git a/src/Avalonia.Base/Threading/IDispatcher.cs b/src/Avalonia.Base/Threading/IDispatcher.cs index 4009dcdeab..1fdc9da5fe 100644 --- a/src/Avalonia.Base/Threading/IDispatcher.cs +++ b/src/Avalonia.Base/Threading/IDispatcher.cs @@ -28,12 +28,17 @@ namespace Avalonia.Threading void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal); /// - /// Post action that will be invoked on main thread + /// Posts an action that will be invoked on the dispatcher thread. /// /// The method. /// The priority with which to invoke the method. - // 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); + + /// + /// Posts a function that will be invoked on the dispatcher thread. + /// + /// The method. + /// The priority with which to invoke the method. + Task InvokeAsync(Func function, DispatcherPriority priority = DispatcherPriority.Normal); } } \ No newline at end of file diff --git a/src/Avalonia.Base/Threading/JobRunner.cs b/src/Avalonia.Base/Threading/JobRunner.cs index c2040a0982..b922370e84 100644 --- a/src/Avalonia.Base/Threading/JobRunner.cs +++ b/src/Avalonia.Base/Threading/JobRunner.cs @@ -14,32 +14,16 @@ namespace Avalonia.Threading /// internal class JobRunner { - - private IPlatformThreadingInterface _platform; - private Queue[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1) - .Select(_ => new Queue()).ToArray(); + private readonly Queue[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1) + .Select(_ => new Queue()).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; - } - /// /// Runs continuations pushed on the loop. /// @@ -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; + } + + /// + /// 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; } /// @@ -105,9 +86,9 @@ namespace Avalonia.Threading _platform = AvaloniaLocator.Current.GetService(); } - 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 + { + /// + /// Gets the job priority. + /// + DispatcherPriority Priority { get; } + + /// + /// Runs the job. + /// + void Run(); + } + /// /// A job to run. /// - private class Job + 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 excepption in TaskCompletionSource + /// Do not wrap exception in TaskCompletionSource public Job(Action action, DispatcherPriority priority, bool throwOnUiThread) { - Action = action; + _action = action; Priority = priority; - TaskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource(); + _taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource(); } + /// + public DispatcherPriority Priority { get; } + /// - /// Gets the method to call. + /// The task. /// - public Action Action { get; } + 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; /// - /// Gets the job priority. + /// Initializes a new instance of the class. /// - public DispatcherPriority Priority { get; } + /// The method to call. + /// The job priority. + public Job(Func function, DispatcherPriority priority) + { + _function = function; + Priority = priority; + _taskCompletionSource = new TaskCompletionSource(); + } + /// + public DispatcherPriority Priority { get; } + /// - /// Gets the task completion source. + /// The task. /// - public TaskCompletionSource TaskCompletionSource { get; } + public Task Task => _taskCompletionSource.Task; + + /// + void IJob.Run() + { + try + { + var result = _function(); + _taskCompletionSource.SetResult(result); + } + catch (Exception e) + { + _taskCompletionSource.SetException(e); + } + } } } } diff --git a/tests/Avalonia.UnitTests/ImmediateDispatcher.cs b/tests/Avalonia.UnitTests/ImmediateDispatcher.cs index 92f64bde6f..44d8c78054 100644 --- a/tests/Avalonia.UnitTests/ImmediateDispatcher.cs +++ b/tests/Avalonia.UnitTests/ImmediateDispatcher.cs @@ -25,6 +25,12 @@ namespace Avalonia.UnitTests return Task.FromResult(null); } + public Task InvokeAsync(Func function, DispatcherPriority priority = DispatcherPriority.Normal) + { + var result = function(); + return Task.FromResult(result); + } + public void VerifyAccess() { }