我很难找到适当的方法来安排长时间运行的反应性财产" getters"在我的ViewModel中。
This excerpt from Intro to RX直截了当地描述了我想要做的事情:
- 回应某种用户操作
- 在后台线程上工作
- 将结果传递回UI线程
- 更新用户界面
仅在这种情况下,除了用户交互之外,我想对其他属性的更改作出反应。
下面是我用来从原始属性获取派生属性的通用模板(在实际代码中,有级联派生属性的链)。
在Reactive ViewModel(继承自ReactiveObject)中,我已经拥有了一些来自其他属性的属性。例如,当Original
更改时,Derived
会重新计算。
public TOriginal Original
{
get { return _original; }
set { this.RaiseAndSetIfChanged(ref _original, value); }
}
TOriginal _original;
public TDerived Derived { get { return _derived.Value; } }
readonly ObservableAsPropertyHelper<double[,]> _derived;
this.WhenAnyValue(x => x.Original)
.Where(originalValue => originalValue != null)
// ObserveOn? SubscribeOn? Which scheduler?
.Select(derivedValue => LongRunningCalculation(originalValue))
// Same thing here: ObserveOn? SubscribeOn? Which scheduler?
.ToProperty(this, x => x.Derived, out _derived); // should I use the `scheduler:` in this method?
我的问题是:我不知道这些不同的设计选择&#34;应该结合使用以获得我想要的响应式UI:
RxApp.TaskpoolScheduler
,RxApp.MainThreadScheduler
,NewThreadScheduler.Default
和其他可能的例子。SubscribeOn
的{{1}} vs ObserveOn
或ObserveOnDispatcher
的{{1}}参数?scheduler:
运算符之前和之后放了重新调度方法,但我不太确定。我坦言,我甚至不确定ToProperty
是否需要。Select
设置为Select
的示例,但我尝试过并且看起来并没什么区别,但同样,也许是因为其他因素。< / LI>
Binding.IsAsync
和true
的概念是否相关?有没有办法在显示的代码中配置它们?SynchronizationContext
还是其他一些ReactiveUI类?最令人伤脑筋的事实是,使用一些组合,计算工作正常,但阻止UI,而使用其他一些组合,值是异步计算的,UI有点较少被阻止,但有时 part 的派生值(例如,在项目集合中)不可用!
很抱歉,如果我问得太多,但我没有找到任何权威的预期方式来完成我在文档中所需的工作。
答案 0 :(得分:2)
在Select
之前可能会阻止在TaskPoolScheduler上观察UI。
在ToProperty
观察MainThreadScheduler之前。
this.WhenAnyValue(x => x.Original)
.Where(originalValue => originalValue != null)
.ObserveOn(TaskPoolScheduler.Default)
.Select(derivedValue => LongRunningCalculation(originalValue))
.ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, x => x.Derived, out _derived);
人们对SubscribeOn
实际做了什么非常困惑。有很多解释。例如,如此处的另一个答案所示
SubscribeOn将可观察链向上移动到顶部并确保observable在给定的调度程序上生成值
这不是真的。查看RX代码库中SubscribeOn
的实现是有益的。你必须跳过几层抽象来实现目标,但最终你会发现。
public static IObservable<TSource>
SubscribeOn<TSource>
( IObservable<TSource> source
, IScheduler scheduler
)
{
if (source == null)
throw new ArgumentNullException("source");
if (scheduler == null)
throw new ArgumentNullException("scheduler");
return (IObservable<TSource>) new AnonymousObservable<TSource>((Func<IObserver<TSource>, IDisposable>) (observer =>
{
SingleAssignmentDisposable assignmentDisposable = new SingleAssignmentDisposable();
SerialDisposable d = new SerialDisposable();
d.Disposable = (IDisposable) assignmentDisposable;
assignmentDisposable.Disposable = scheduler.Schedule((Action) (() => d.Disposable = (IDisposable) new ScheduledDisposable(scheduler, source.SubscribeSafe<TSource>(observer))));
return (IDisposable) d;
}));
}
这样做的唯一方法是确保在指定的调度程序上调用Subscribe
上的source
方法,并确保同一Dispose
返回的Subscribe
方法在指定的调度程序上也调用了方法。这对下游代码的影响各不相同。
例如
using System;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace SubscribeOnVsObserveOn
{
class Program
{
static readonly Subject<object> EventsSubject = new Subject<object>();
private static readonly IObservable<object> Events = Observable.Create<object>
( observer =>
{
Info( "Subscribing" );
return EventsSubject.Subscribe( observer );
} );
public static void Info(string msg)
{
var currentThread = Thread.CurrentThread;
var currentThreadName = string.IsNullOrWhiteSpace( currentThread.Name ) ? "<no name>" : currentThread.Name;
Console.WriteLine
( $"Thread Id {currentThread.ManagedThreadId} {currentThreadName} - " + msg );
}
public static void Foo()
{
Thread.CurrentThread.Name = "Main Thread";
Info( "Starting" );
void OnNext(object o) => Info( $"Received {o}" );
void Notify(object obj)
{
Info( $"Sending {obj}" );
EventsSubject.OnNext( obj );
}
void StartAndSend(object o, string threadName)
{
var thread = new Thread(Notify);
thread.Name = threadName;
thread.Start(o);
thread.Join();
}
Notify(1);
Console.WriteLine("=============================================" );
Console.WriteLine("Subscribe Only" );
Console.WriteLine("=============================================" );
using (Events.Subscribe(OnNext))
{
Thread.Sleep( 200 );
StartAndSend(2, "A");
StartAndSend(3, "B");
}
Console.WriteLine("=============================================" );
Console.WriteLine("Subscribe With SubscribeOn(CurrentThreadScheduler)" );
Console.WriteLine("=============================================" );
using (Events.SubscribeOn( CurrentThreadScheduler.Instance ).Subscribe(OnNext))
{
Thread.Sleep( 200 );
StartAndSend(2, "A");
StartAndSend(3, "B");
}
Console.WriteLine("=============================================" );
Console.WriteLine("Subscribe With SubscribeOn(ThreadPool)" );
Console.WriteLine("=============================================" );
using (Events.SubscribeOn( ThreadPoolScheduler.Instance ).Subscribe(OnNext))
{
Thread.Sleep( 200 );
StartAndSend(2, "A");
StartAndSend(3, "B");
}
Console.WriteLine("=============================================" );
Console.WriteLine("Subscribe With SubscribeOn(NewThread)" );
Console.WriteLine("=============================================" );
using (Events.SubscribeOn( NewThreadScheduler.Default ).Subscribe(OnNext))
{
Thread.Sleep( 200 );
StartAndSend(2, "A");
StartAndSend(3, "B");
}
Console.WriteLine("=============================================" );
Console.WriteLine("Subscribe With SubscribeOn(NewThread) + ObserveOn" );
Console.WriteLine("=============================================" );
using (Events.SubscribeOn( NewThreadScheduler.Default ).ObserveOn(TaskPoolScheduler.Default ).Subscribe(OnNext))
{
Thread.Sleep( 200 );
StartAndSend(2, "A");
StartAndSend(3, "B");
}
}
static void Main(string[] args)
{
Foo();
Console.WriteLine( "Press Any Key" );
Console.ReadLine();
}
}
}
生成以下输出
Thread Id 1 Main Thread - Starting
Thread Id 1 Main Thread - Sending 1
=============================================
Subscribe Only
=============================================
Thread Id 1 Main Thread - Subscribing
Thread Id 4 A - Sending 2
Thread Id 4 A - Received 2
Thread Id 5 B - Sending 3
Thread Id 5 B - Received 3
=============================================
Subscribe With SubscribeOn(CurrentThreadScheduler)
=============================================
Thread Id 1 Main Thread - Subscribing
Thread Id 6 A - Sending 2
Thread Id 6 A - Received 2
Thread Id 7 B - Sending 3
Thread Id 7 B - Received 3
=============================================
Subscribe With SubscribeOn(ThreadPool)
=============================================
Thread Id 8 <no name> - Subscribing
Thread Id 10 A - Sending 2
Thread Id 10 A - Received 2
Thread Id 11 B - Sending 3
Thread Id 11 B - Received 3
=============================================
Subscribe With SubscribeOn(NewThread)
=============================================
Thread Id 12 <no name> - Subscribing
Thread Id 13 A - Sending 2
Thread Id 13 A - Received 2
Thread Id 14 B - Sending 3
Thread Id 14 B - Received 3
=============================================
Subscribe With SubscribeOn(NewThread) + ObserveOn
=============================================
Thread Id 16 <no name> - Subscribing
Thread Id 17 A - Sending 2
Thread Id 19 B - Sending 3
Thread Id 18 <no name> - Received 2
Thread Id 18 <no name> - Received 3
Press Any Key
值得注意的是,SubscribeOn既不能强制发送或接收事件在特定的调度程序上。它只能强制Subscribe
方法在特定的调度程序上发生。 可能或可能具有下游/上游效果。
答案 1 :(得分:1)
在Rx.NET中有一些调度程序,包括一个专用于WPF的调度程序。
TaskPoolScheduler
在任务池上运行代码。这有点像在Task
内运行代码。NewThreadScheduler
生成一个新线程来运行代码。通常不要使用此运算符,除非您知道“需要”它(您几乎从不这样做)DispatcherScheduler
在UI线程上运行代码。当您要在VM中设置属性时使用此选项 RxUI带来两个平台无关的调度程序抽象。无论你使用什么平台(WPF,UWP,Xamarin.iOS,Xamarin.Android)RxApp.MainThreadScheduler
将始终引用UI线程调度程序,而RxApp.TaskPoolScheduler
将引用类似于后台线程的东西
如果您想保持简单,只需使用RxApp
调度程序;用于UI内容的RxApp.MainThreadScheduler
和用于后台/重型内容的RxApp.TaskPoolScheduler
。
名称SubscribeOn()
有点令人困惑,因为它不会直接影响Subscribe()
方法。 SubscribeOn()
决定observable将从哪个调度程序开始;将在哪个调度程序上完成原始/第一个订阅(不是Subscribe()
方法将在哪个调度程序上执行)。我喜欢认为SubsribeOn()
将可观察链向上移动到顶部并确保observable在给定的调度程序上生成值。
有些运营商允许您指定应运行哪个调度程序。当他们这样做时,你应该总是喜欢传递一个调度程序,这样你就知道他们要去哪里工作并防止他们潜在地阻止他们(虽然他们不应该)。 SubsribeOn()
对于不能让您指定调度程序的可观察对象来说是一种“黑客”。如果使用SubscribeOn()
,但运算符指定了调度程序,则运算符调度程序将发出来自运算符的信号,而不是SubscribeOn()
中指定的运算符调度程序。
ObserveOn()
与SubscribeOn()
的功能大致相同,但它“从此时开始”。 ObserveOn()
之后的运算符和代码将在给ObserveOn()
的调度程序上执行。我喜欢认为ObserveOn()
意味着“改变这个线程”。
如果你要做繁重的工作,把它放在一个函数中并调用该函数,就像你用LongRunningCalculation()
所做的那样。您可以在ObserveOn(RxApp.TaskPoolScheduler)
之前使用Select()
,在ObserveOn(RxApp.MainThreadScheduler
之后使用Observable.Start()
,但我更喜欢将SelectMany()
与Observable.Start()
结合使用。
Observable.Return()
基本上是SelectMany()
:“将此函数的结果作为可观察对象给出。”您还可以指定它应该调用函数的调度程序。
await
确保我们得到observable的结果,而不是observable本身。 (它有点像{oberables的WhenAnyValue()
:“在我们得到这个可观察的结果之前不要执行这个下一个运算符”)
您正在执行派生属性。
使用ToProperty()
获取属性和管道更改为INotifyPropertyChanged
。你放在它们之间的运算符可以在后台线程上工作延迟派生属性的设置,但这就是我们public TOriginal Original
{
get { return _original; }
set { this.RaiseAndSetIfChanged(ref _original, value); }
}
TOriginal _original;
public TDerived Derived { get { return _derived.Value; } }
readonly ObservableAsPropertyHelper<double[,]> _derived;
_derived = this.WhenAnyValue(x => x.Original)
.Where(originalValue => originalValue != null)
// Sepcify the scheduler to the operator directly
.SelectMany(originalValue =>
Observable.Start(
() => LongRunningCalculation(originalValue),
RxApp.TaskPoolScheduler))
.ObserveOn(RxApp.MainThreadScheduler)
// I prefer this overload of ToProperty, which returns an ObservableAsPropertyHelper
.ToProperty(this, x => x.Derived);
的原因。
以下是我将如何实现您的具体示例:
-- ==============================
-- Create External Table Template
-- ==============================
USE <database_name, sysname, AdventureWorks>
GO
IF OBJECT_ID('<schema_name, sysname, dbo>.<table_name, sysname, sample_external_table>', 'U') IS NOT NULL
DROP EXTERNAL TABLE <schema_name, sysname, dbo>.<table_name, sysname, sample_external_table>
GO
CREATE EXTERNAL TABLE <schema_name, sysname, dbo>.<table_name, sysname, sample_external_table>
(
<column1_name, sysname, c1> <column1_datatype, , int> <column1_nullability, , NOT NULL>,
<column2_name, sysname, c2> <column2_datatype, , char(10)> <column2_nullability, , NULL>,
<column3_name, sysname, c3> <column3_datatype, , datetime> <column3_nullability, , NULL>
)
WITH
(
LOCATION = N'<location, nvarchar(3000), sample_location>',
DATA_SOURCE = <data_source_name, sysname, sample_data_source>,
FILE_FORMAT = <file_format_name, sysname, sample_file_format>,
REJECT_TYPE = <reject_type, nvarchar(20), sample_reject_type>,
REJECT_VALUE = <reject_value, float, sample_reject_value>,
REJECT_SAMPLE_VALUE = <reject_sample_value, float,
sample_reject_sample_value>
)
GO
我们有一个Slack团队为ReactiveUI欢迎您加入。您可以点击here
来要求邀请