Browse Source

Use custom synchronous task-like awaitable

pull/2705/head
Nikita Tsukanov 7 years ago
parent
commit
6a90251654
  1. 5
      build/Base.props
  2. 106
      src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs
  3. 37
      src/Avalonia.Styling/Controls/ChildNameScope.cs
  4. 3
      src/Avalonia.Styling/Controls/INameScope.cs
  5. 17
      src/Avalonia.Styling/Controls/NameScope.cs
  6. 21
      src/Avalonia.Styling/Controls/NameScopeLocator.cs

5
build/Base.props

@ -1,10 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<!--
Do not upgrade, see
https://github.com/xamarin/xamarin-android/issues/1879#issuecomment-412281057
-->
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.4.0" />
</ItemGroup>
</Project>

106
src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Avalonia.Utilities
{
/// <summary>
/// A task-like operation that is guaranteed to finish continuations synchronously,
/// can be used for parametrized one-shot events
/// </summary>
public struct SynchronousCompletionAsyncResult<T> : INotifyCompletion
{
private readonly SynchronousCompletionAsyncResultSource<T> _source;
private readonly T _result;
private readonly bool _isValid;
internal SynchronousCompletionAsyncResult(SynchronousCompletionAsyncResultSource<T> source)
{
_source = source;
_result = default;
_isValid = true;
}
public SynchronousCompletionAsyncResult(T result)
{
_result = result;
_source = null;
_isValid = true;
}
static void ThrowNotInitialized() =>
throw new InvalidOperationException("This SynchronousCompletionAsyncResult was not initialized");
public bool IsCompleted
{
get
{
if (!_isValid)
ThrowNotInitialized();
return _source == null || _source.IsCompleted;
}
}
public T GetResult()
{
if (!_isValid)
ThrowNotInitialized();
return _source == null ? _result : _source.Result;
}
public void OnCompleted(Action continuation)
{
if (!_isValid)
ThrowNotInitialized();
if (_source == null)
continuation();
else
_source.OnCompleted(continuation);
}
public SynchronousCompletionAsyncResult<T> GetAwaiter() => this;
}
/// <summary>
/// Source for incomplete SynchronousCompletionAsyncResult
/// </summary>
/// <typeparam name="T"></typeparam>
public class SynchronousCompletionAsyncResultSource<T>
{
private T _result;
internal bool IsCompleted { get; private set; }
public SynchronousCompletionAsyncResult<T> AsyncResult => new SynchronousCompletionAsyncResult<T>(this);
internal T Result => IsCompleted ?
_result :
throw new InvalidOperationException("Asynchronous operation is not yet completed");
private List<Action> _continuations;
internal void OnCompleted(Action continuation)
{
if(_continuations==null)
_continuations = new List<Action>();
_continuations.Add(continuation);
}
public void SetResult(T result)
{
if (IsCompleted)
throw new InvalidOperationException("Asynchronous operation is already completed");
_result = result;
IsCompleted = true;
if(_continuations!=null)
foreach (var c in _continuations)
c();
_continuations = null;
}
public void TrySetResult(T result)
{
if(IsCompleted)
return;
SetResult(result);
}
}
}

37
src/Avalonia.Styling/Controls/ChildNameScope.cs

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
@ -14,27 +15,45 @@ namespace Avalonia.Controls
public void Register(string name, object element) => _inner.Register(name, element);
public ValueTask<object> FindAsync(string name)
public SynchronousCompletionAsyncResult<object> FindAsync(string name)
{
var found = Find(name);
if (found != null)
return new ValueTask<object>(found);
// Not found and both current and parent scope are in completed stage
return new SynchronousCompletionAsyncResult<object>(found);
// Not found and both current and parent scope are in completed state
if(IsCompleted)
return new ValueTask<object>(null);
return new SynchronousCompletionAsyncResult<object>(null);
return DoFindAsync(name);
}
async ValueTask<object> DoFindAsync(string name)
public SynchronousCompletionAsyncResult<object> DoFindAsync(string name)
{
var src = new SynchronousCompletionAsyncResultSource<object>();
void ParentSearch()
{
var parentSearch = _parentScope.FindAsync(name);
if (parentSearch.IsCompleted)
src.SetResult(parentSearch.GetResult());
else
parentSearch.OnCompleted(() => src.SetResult(parentSearch.GetResult()));
}
if (!_inner.IsCompleted)
{
var found = await _inner.FindAsync(name);
if (found != null)
return found;
// Guaranteed to be incomplete at this point
var innerSearch = _inner.FindAsync(name);
innerSearch.OnCompleted(() =>
{
var value = innerSearch.GetResult();
if (value != null)
src.SetResult(value);
else ParentSearch();
});
}
else
ParentSearch();
return await _parentScope.FindAsync(name);
return src.AsyncResult;
}
public object Find(string name)

3
src/Avalonia.Styling/Controls/INameScope.cs

@ -3,6 +3,7 @@
using System;
using System.Threading.Tasks;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
@ -24,7 +25,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The element, or null if the name was not found.</returns>
ValueTask<object> FindAsync(string name);
SynchronousCompletionAsyncResult<object> FindAsync(string name);
/// <summary>
/// Finds a named element in the name scope, returns immediately, doesn't traverse the name scope stack

17
src/Avalonia.Styling/Controls/NameScope.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.LogicalTree;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
@ -24,8 +25,8 @@ namespace Avalonia.Controls
private readonly Dictionary<string, object> _inner = new Dictionary<string, object>();
private readonly Dictionary<string, TaskCompletionSource<object>> _pendingSearches =
new Dictionary<string, TaskCompletionSource<object>>();
private readonly Dictionary<string, SynchronousCompletionAsyncResultSource<object>> _pendingSearches =
new Dictionary<string, SynchronousCompletionAsyncResultSource<object>>();
/// <summary>
/// Gets the value of the attached <see cref="NameScopeProperty"/> on a styled element.
@ -79,18 +80,18 @@ namespace Avalonia.Controls
}
}
public ValueTask<object> FindAsync(string name)
public SynchronousCompletionAsyncResult<object> FindAsync(string name)
{
var found = Find(name);
if (found != null)
return new ValueTask<object>(found);
return new SynchronousCompletionAsyncResult<object>(found);
if (IsCompleted)
return new ValueTask<object>((object)null);
return new SynchronousCompletionAsyncResult<object>((object)null);
if (!_pendingSearches.TryGetValue(name, out var tcs))
// We are intentionally running continuations synchronously here
_pendingSearches[name] = tcs = new TaskCompletionSource<object>();
return new ValueTask<object>(tcs.Task);
_pendingSearches[name] = tcs = new SynchronousCompletionAsyncResultSource<object>();
return tcs.AsyncResult;
}
/// <inheritdoc />

21
src/Avalonia.Styling/Controls/NameScopeLocator.cs

@ -6,6 +6,7 @@ using System.Reflection;
using System.Threading.Tasks;
using Avalonia.LogicalTree;
using Avalonia.Reactive;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
@ -20,31 +21,31 @@ namespace Avalonia.Controls
/// <param name="name">The name of the control to find.</param>
public static IObservable<object> Track(INameScope scope, string name)
{
return new NeverEndingValueTaskObservable<object>(scope.FindAsync(name));
return new NeverEndingSynchronousCompletionAsyncResultObservable<object>(scope.FindAsync(name));
}
// This class is implemented in such weird way because for some reason
// our binding system doesn't expect OnCompleted to be ever called and
// seems to treat it as binding cancellation or something
private class NeverEndingValueTaskObservable<T> : IObservable<T>
private class NeverEndingSynchronousCompletionAsyncResultObservable<T> : IObservable<T>
{
private T _value;
private Task<T> _task;
private SynchronousCompletionAsyncResult<T>? _task;
public NeverEndingValueTaskObservable(ValueTask<T> task)
public NeverEndingSynchronousCompletionAsyncResultObservable(SynchronousCompletionAsyncResult<T> task)
{
if (task.IsCompleted)
_value = task.Result;
_value = task.GetResult();
else
_task = task.AsTask();
_task = task;
}
public IDisposable Subscribe(IObserver<T> observer)
{
if (_task?.IsCompleted == true)
{
_value = _task.Result;
_value = _task.Value.GetResult();
_task = null;
}
@ -52,10 +53,10 @@ namespace Avalonia.Controls
// We expect everything to handle callbacks synchronously,
// so the object graph is ready after its built
// so keep TaskContinuationOptions.ExecuteSynchronously
_task.ContinueWith(t =>
_task.Value.OnCompleted(() =>
{
observer.OnNext(t.Result);
}, TaskContinuationOptions.ExecuteSynchronously);
observer.OnNext(_task.Value.GetResult());
});
else
observer.OnNext(_value);

Loading…
Cancel
Save