diff --git a/readme.md b/readme.md index 0da6980146..0cc2b05e9d 100644 --- a/readme.md +++ b/readme.md @@ -1,9 +1,9 @@ # Avalonia -[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) -[![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) -[![Build Status](https://travis-ci.org/AvaloniaUI/Avalonia.svg?branch=master)](https://travis-ci.org/AvaloniaUI/Avalonia) +| Gitter Chat | Windows Build Status | Linux/Mac Build Status | Code Coverage | +|---|---|---|---| +| [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Build Status](https://travis-ci.org/AvaloniaUI/Avalonia.svg?branch=master)](https://travis-ci.org/AvaloniaUI/Avalonia) | [![codecov](https://codecov.io/gh/AvaloniaUI/Avalonia/branch/master/graph/badge.svg)](https://codecov.io/gh/AvaloniaUI/Avalonia) | A multi-platform .NET UI framework. It can run on Windows, Linux, Mac OS X, iOS and Android. diff --git a/samples/BindingTest/MainWindow.xaml b/samples/BindingTest/MainWindow.xaml index 02c364346d..95f671fd84 100644 --- a/samples/BindingTest/MainWindow.xaml +++ b/samples/BindingTest/MainWindow.xaml @@ -41,6 +41,10 @@ + + + + diff --git a/samples/BindingTest/ViewModels/MainWindowViewModel.cs b/samples/BindingTest/ViewModels/MainWindowViewModel.cs index 94f7ff595a..4b58bf2279 100644 --- a/samples/BindingTest/ViewModels/MainWindowViewModel.cs +++ b/samples/BindingTest/ViewModels/MainWindowViewModel.cs @@ -3,6 +3,8 @@ using System.Collections.ObjectModel; using System.Linq; using ReactiveUI; using System.Reactive.Linq; +using System.Threading.Tasks; +using System.Threading; namespace BindingTest.ViewModels { @@ -12,6 +14,7 @@ namespace BindingTest.ViewModels private double _doubleValue = 5.0; private string _stringValue = "Simple Binding"; private bool _booleanFlag = false; + private string _currentTime; public MainWindowViewModel() { @@ -37,6 +40,15 @@ namespace BindingTest.ViewModels BooleanFlag = !BooleanFlag; StringValue = param.ToString(); }); + + Task.Run(() => + { + while (true) + { + CurrentTime = DateTimeOffset.Now.ToString(); + Thread.Sleep(1000); + } + }); } public ObservableCollection Items { get; } @@ -67,6 +79,12 @@ namespace BindingTest.ViewModels set { this.RaiseAndSetIfChanged(ref _booleanFlag, value); } } + public string CurrentTime + { + get { return _currentTime; } + private set { this.RaiseAndSetIfChanged(ref _currentTime, value); } + } + public ReactiveCommand StringValueCommand { get; } public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel(); diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 48e937d6b2..409abfe8fa 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -12,6 +12,7 @@ using Avalonia.Diagnostics; using Avalonia.Logging; using Avalonia.Threading; using Avalonia.Utilities; +using System.Reactive.Concurrency; namespace Avalonia { @@ -304,6 +305,11 @@ namespace Avalonia VerifyAccess(); + var description = GetDescription(source); + + var scheduler = AvaloniaLocator.Current.GetService() ?? ImmediateScheduler.Instance; + source = source.ObserveOn(scheduler); + if (property.IsDirect) { if (property.IsReadOnly) @@ -316,7 +322,7 @@ namespace Avalonia this, "Bound {Property} to {Binding} with priority LocalValue", property, - GetDescription(source)); + description); IDisposable subscription = null; @@ -358,7 +364,7 @@ namespace Avalonia this, "Bound {Property} to {Binding} with priority {Priority}", property, - GetDescription(source), + description, priority); return v.Add(source, (int)priority); diff --git a/src/Avalonia.Base/Threading/AvaloniaScheduler.cs b/src/Avalonia.Base/Threading/AvaloniaScheduler.cs index de32057a00..f9d67470c1 100644 --- a/src/Avalonia.Base/Threading/AvaloniaScheduler.cs +++ b/src/Avalonia.Base/Threading/AvaloniaScheduler.cs @@ -3,6 +3,7 @@ using System; using System.Reactive.Concurrency; +using System.Reactive.Disposables; namespace Avalonia.Threading { @@ -26,13 +27,31 @@ namespace Avalonia.Threading /// public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) { - return DispatcherTimer.Run( - () => + var composite = new CompositeDisposable(2); + if (dueTime == TimeSpan.Zero) + { + if (!Dispatcher.UIThread.CheckAccess()) { - action(this, state); - return false; - }, - dueTime); + var cancellation = new CancellationDisposable(); + Dispatcher.UIThread.InvokeAsync(() => + { + if (!cancellation.Token.IsCancellationRequested) + { + composite.Add(action(this, state)); + } + }, DispatcherPriority.DataBind); + composite.Add(cancellation); + } + else + { + return action(this, state); + } + } + else + { + composite.Add(DispatcherTimer.RunOnce(() => composite.Add(action(this, state)), dueTime)); + } + return composite; } } } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 326556f629..3d13608226 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -11,6 +11,7 @@ using Avalonia.Layout; using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.Threading; +using System.Reactive.Concurrency; namespace Avalonia { @@ -175,7 +176,8 @@ namespace Avalonia .Bind().ToTransient() .Bind().ToConstant(_styler) .Bind().ToSingleton() - .Bind().ToConstant(this); + .Bind().ToConstant(this) + .Bind().ToConstant(AvaloniaScheduler.Instance); } } } diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs index e5ede1c6b4..5c02be7d5b 100644 --- a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs +++ b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs @@ -16,10 +16,7 @@ namespace Avalonia.Shared.PlatformSupport public void PostThreadPoolItem(Action cb) => ThreadPool.UnsafeQueueUserWorkItem(_ => cb(), null); public IDisposable StartSystemTimer(TimeSpan interval, Action tick) { - var timer = new Timer(delegate - { - - }, null, interval, interval); + var timer = new Timer(_ => tick(), null, interval, interval); return Disposable.Create(() => timer.Dispose()); } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 66fe3c7767..5e286305d2 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -11,6 +11,13 @@ using Avalonia.Data; using Avalonia.Logging; using Avalonia.UnitTests; using Xunit; +using System.Threading.Tasks; +using Avalonia.Platform; +using System.Threading; +using Moq; +using System.Reactive.Disposables; +using System.Reactive.Concurrency; +using Avalonia.Threading; namespace Avalonia.Base.UnitTests { @@ -356,6 +363,29 @@ namespace Avalonia.Base.UnitTests Assert.True(called); } } + + [Fact] + public async void Bind_With_Scheduler_Executes_On_Scheduler() + { + var target = new Class1(); + var source = new Subject(); + var currentThreadId = Thread.CurrentThread.ManagedThreadId; + + var threadingInterfaceMock = new Mock(); + threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread) + .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId); + + using (AvaloniaLocator.EnterScope()) + { + AvaloniaLocator.CurrentMutable.Bind().ToConstant(threadingInterfaceMock.Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(AvaloniaScheduler.Instance); + + target.Bind(Class1.QuxProperty, source); + + await Task.Run(() => source.OnNext(6.7)); + } + + } /// /// Returns an observable that returns a single value but does not complete.