[英]Is it necessary to synchronize observables when observing on the main thread?
[英]How to synchronize Observables and offload UI Thread
我有兩個簡單的觀察處理程序,訂閱相同的源。 但是,兩個訂閱都在不同類型上運行。 我希望它們保持可觀察源(Subject())的順序。 我嘗試使用Synchronize()擴展,但我找不到按預期方式工作的方法。
這是我的單元測試代碼:
[Test]
public void TestObserveOn()
{
Console.WriteLine("Starting on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
var source = new Subject<object>();
var are = new AutoResetEvent(false);
using (source.ObserveOn(TaskPoolScheduler.Default).Synchronize(source).OfType<int>().Subscribe(
o =>
{
Console.WriteLine("Received {1} on threadId:{0}", Thread.CurrentThread.ManagedThreadId, o);
int sleep = 3000 / o; // just to simulate longer processing
Thread.Sleep(sleep);
Console.WriteLine("Handled {1} on threadId: {0}", Thread.CurrentThread.ManagedThreadId, o);
},
() =>
{
Console.WriteLine("OnCompleted on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
are.Set();
}))
using (source.ObserveOn(TaskPoolScheduler.Default).Synchronize(source).OfType<double>().Subscribe(
o =>
{
Console.WriteLine("Received {1} on threadId:{0}", Thread.CurrentThread.ManagedThreadId, o);
Console.WriteLine("Handled {1} on threadId: {0}", Thread.CurrentThread.ManagedThreadId, o);
},
() =>
{
Console.WriteLine("OnCompleted on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
}))
{
Console.WriteLine("Subscribed on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
source.OnNext(1);
source.OnNext(1.1);
source.OnNext(2);
source.OnNext(2.1);
source.OnNext(3);
source.OnNext(3.1);
source.OnCompleted();
Console.WriteLine("Finished on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
are.WaitOne();
}
}
得到的測試代碼輸出:
Starting on threadId:10
Subscribed on threadId:10
Finished on threadId:10
Received 1 on threadId:11
Handled 1 on threadId: 11
Received 1,1 on threadId:12
Handled 1,1 on threadId: 12
Received 2,1 on threadId:12
Handled 2,1 on threadId: 12
Received 3,1 on threadId:12
Handled 3,1 on threadId: 12
Received 2 on threadId:11
Handled 2 on threadId: 11
OnCompleted on threadId:12
Received 3 on threadId:11
Handled 3 on threadId: 11
OnCompleted on threadId:11
如您所見,訂單與輸入不同。 我想同步兩個訂閱,以便順序與輸入相同。
輸出應該是
Starting on threadId:10
Subscribed on threadId:10
Finished on threadId:10
Received 1 on threadId:11
Handled 1 on threadId: 11
Received 1,1 on threadId:12
Handled 1,1 on threadId: 12
Received 2 on threadId:11
Handled 2 on threadId: 11
Received 2,1 on threadId:12
Handled 2,1 on threadId: 12
Received 3 on threadId:11
Handled 3 on threadId: 11
Received 3,1 on threadId:12
Handled 3,1 on threadId: 12
OnCompleted on threadId:11
OnCompleted on threadId:12
(完成順序對我來說並不重要)。
編輯:
我也嘗試過以下方法:
[Test]
public void TestObserveOn()
{
Console.WriteLine("Starting on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
var source = new Subject<object>();
var taskSchedulerPair = new ConcurrentExclusiveSchedulerPair();
var exclusiveTaskFactory = new TaskFactory(taskSchedulerPair.ExclusiveScheduler);
var exclusiveScheduler = new TaskPoolScheduler(exclusiveTaskFactory);
var are = new AutoResetEvent(false);
using (source.ObserveOn(exclusiveScheduler).OfType<int>().Subscribe(
o =>
{
Console.WriteLine("Received {1} on threadId:{0}", Thread.CurrentThread.ManagedThreadId, o);
int sleep = 3000 / o;
Thread.Sleep(sleep);
Console.WriteLine("Handled {1} on threadId: {0}", Thread.CurrentThread.ManagedThreadId, o);
},
() =>
{
Console.WriteLine("OnCompleted on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
are.Set();
}))
using (source.ObserveOn(exclusiveScheduler).OfType<double>().Subscribe(
o =>
{
Console.WriteLine("Received {1} on threadId:{0}", Thread.CurrentThread.ManagedThreadId, o);
Console.WriteLine("Handled {1} on threadId: {0}", Thread.CurrentThread.ManagedThreadId, o);
},
() =>
{
Console.WriteLine("OnCompleted on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
are.Set();
}))
{
Console.WriteLine("Subscribed on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
source.OnNext(1);
source.OnNext(1.1);
source.OnNext(2);
source.OnNext(2.1);
source.OnNext(3);
source.OnNext(3.1);
source.OnCompleted();
Console.WriteLine("Finished on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
are.WaitOne();
are.WaitOne();
}
}
但輸出仍然是錯誤的:
Starting on threadId:10
Subscribed on threadId:10
Finished on threadId:10
Received 1 on threadId:4
Handled 1 on threadId: 4
Received 2 on threadId:4
Handled 2 on threadId: 4
Received 3 on threadId:4
Handled 3 on threadId: 4
OnCompleted on threadId:4
Received 1,1 on threadId:4
Handled 1,1 on threadId: 4
Received 2,1 on threadId:4
Handled 2,1 on threadId: 4
Received 3,1 on threadId:4
Handled 3,1 on threadId: 4
OnCompleted on threadId:4
...因為你可以看到它不是OnNext()調用的順序。
當使用具有類似create的含義的類型然后執行多個更新時,這一點尤其重要...如果更新在創建之前怎么辦? 如果訂單無法保證您可能遇到問題或需要將“未來”事件排隊,直到其前任與要更改的狀態同步。 你需要像增加版本/訂單號這樣的東西來使用它作為訂購標准並找到“漏洞”並對后繼者進行排隊,直到它們再次排成一行。
第二次編輯 ......更接近我的問題並退出測試案例理論:
我想要一個易於使用RX過濾功能的簡單界面:
public interface ICommandBus // or to say Aggregator pattern
{
void Send<T>(T command) where T : ICommand; // might be something like Task<Result> Send<T>(T command) to know the system has accepted the command
IObservable<T> Stream<T>() where T : ICommand;
}
public class CommandBus : ICommandBus, IDisposable
{
private static readonly ILog Log = LogManager.GetLogger<CommandBus>();
private readonly HashSet<Type> registrations = new HashSet<Type>();
private readonly Subject<ICommand> stream = new Subject<ICommand>();
private readonly IObservable<ICommand> notifications;
private bool disposed;
public CommandBus()
{
// hmm, this is a problem!? how to sync?
this.notifications = this.stream.SubscribeOn(TaskPoolScheduler.Default);
}
public IObservable<T> Stream<T>() where T : ICommand
{
var observable = this.notifications.OfType<T>();
return new ExclusiveObservableWrapper<T>(
observable,
t => this.registrations.Add(t),
t => this.registrations.Remove(t));
}
public void Send<T>(T command) where T : ICommand
{
if (command == null)
{
throw new ArgumentNullException("command");
}
if (!this.registrations.Contains(typeof(T)))
{
throw new NoCommandHandlerSubscribedException();
}
Log.Debug(logm => logm("Sending command of type {0}.", typeof(T).Name));
this.stream.OnNext(command);
}
//public async Task SendAsync<T>(T command) where T : ICommand
//{
// if (command == null)
// {
// throw new ArgumentNullException("command");
// }
// if (!this.registrations.Contains(typeof(T)))
// {
// throw new NoCommandHandlerSubscribedException();
// }
// Log.Debug(logm => logm("Sending command of type {0}.", typeof(T)));
// this.stream.OnNext(command);
// await this.stream.Where(item => ReferenceEquals(item, command));
//}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
this.stream.Dispose();
}
}
this.disposed = true;
}
[Serializable]
public class CommandAlreadySubscribedException : Exception
{
internal CommandAlreadySubscribedException(Type type)
: base(string.Format("Tried to subscribe handler for command of type {0} but there was already a subscribtion. More than one handler at time is not allowed.", type))
{
}
protected CommandAlreadySubscribedException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
[Serializable]
public class NoCommandHandlerSubscribedException : Exception
{
public NoCommandHandlerSubscribedException()
{
}
public NoCommandHandlerSubscribedException(string message)
: base(message)
{
}
public NoCommandHandlerSubscribedException(string message, Exception innerException)
: base(message, innerException)
{
}
protected NoCommandHandlerSubscribedException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
private class ExclusiveObservableWrapper<T> : IObservable<T> where T : ICommand
{
private readonly IObservable<T> observable;
private readonly Func<Type, bool> register;
private readonly Action<Type> unregister;
internal ExclusiveObservableWrapper(IObservable<T> observable, Func<Type, bool> register, Action<Type> unregister)
{
this.observable = observable;
this.register = register;
this.unregister = unregister;
}
public IDisposable Subscribe(IObserver<T> observer)
{
var subscription = this.observable.Subscribe(observer);
var type = typeof(T);
if (!this.register(type))
{
observer.OnError(new CommandAlreadySubscribedException(type));
}
return Disposable.Create(
() =>
{
subscription.Dispose();
this.unregister(type);
});
}
}
}
如果我不能保證命令在順序中(如給定的那樣)那么它們(可能)沒有任何意義。 (創建前更新)
ICommandBus用於想要為命令調用相應處理程序的UI / Presentation層(無需知道處理程序)。
我想簡單地將鏈卸載到一個單獨的線程。
命令 - >總線 - >命令處理程序 - >域模型 - >事件 - >事件處理程序 - >讀取模型
這需要按順序保持命令。
我認為RX能夠通過一些“魔術線”來做到這一點。 但據我所知,現在我必須用自己的線程處理再做一次。 :-(
您似乎對.Synchronize()
作用有錯誤的理解。 它的唯一目的是獲取一個生成重疊或OnCompleted
消息的可觀察對象(即在OnNext
或多個OnError
之前的OnCompleted
)並確保它們遵循OnNext*(OnError|OnCompleted)
行為契約。 這是為了使流氓可觀察的游戲變得更好。
現在,因為我們可以忽略它,因為您的示例輸入是一個表現良好的可觀察對象,那么您可以通過調用.ObserveOn(TaskPoolScheduler.Default)
來看到您正在制作可觀察的跳轉線程 - 這很容易導致在不同的情況下消耗可觀察量利率 - 這就是這里發生的事情。
您已經訂閱了兩次source
,因此您無法根據引入並發性的方式停止您所看到的行為。
鑒於你之前的問題( 如何等待完成的IObserver調用,包括觀察用戶調用? ),你似乎一心想用Rx來增加並發性,但是然后強迫它以某種方式刪除它。 你真的應該放開釋放Rx的心態來做它的事情而不是扼殺它。
編輯@Beachwalker:
在對這個答案的評論中, Enigmativity給出了我的問題的正確答案 。
我必須使用EventLoopScheduler 。 所以我接受這個作為正確答案。
為了完整。 這是有效的代碼:
[Test]
public void TestObserveOn()
{
Console.WriteLine("Starting on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
var source = new Subject<object>();
var exclusiveScheduler = new EventLoopScheduler();
var are = new AutoResetEvent(false);
using (source.ObserveOn(exclusiveScheduler).OfType<int>().Subscribe(
o =>
{
Console.WriteLine("Received {1} on threadId:{0}", Thread.CurrentThread.ManagedThreadId, o);
int sleep = 3000 / o;
Thread.Sleep(sleep);
Console.WriteLine("Handled {1} on threadId: {0}", Thread.CurrentThread.ManagedThreadId, o);
},
() =>
{
Console.WriteLine("OnCompleted on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
are.Set();
}))
using (source.ObserveOn(exclusiveScheduler).OfType<double>().Subscribe(
o =>
{
Console.WriteLine(
"Received {1} on threadId:{0}",
Thread.CurrentThread.ManagedThreadId,
o);
Console.WriteLine(
"Handled {1} on threadId: {0}",
Thread.CurrentThread.ManagedThreadId,
o);
},
() =>
{
Console.WriteLine("OnCompleted on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
are.Set();
}))
{
Console.WriteLine("Subscribed on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
source.OnNext(1);
source.OnNext(1.1);
source.OnNext(2);
source.OnNext(2.1);
source.OnNext(3);
source.OnNext(3.1);
source.OnCompleted();
Console.WriteLine("Finished on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
are.WaitOne();
are.WaitOne();
}
}
您可以根據源下一個成員的類型過濾為source
創建兩個不同的任務。
您可以從線程ID中看到並行處理消息。 這為您提供了更好的性能,但不保證您處理source
的順序。 因此,如果您需要對象的順序句柄,則必須重寫代碼以進行順序執行(這會降低性能)或使用其他調度程序進行測試。
目前您正在使用TaskPoolScheduler.Default
, TaskPoolScheduler.Default
使用默認線程池。 所以你可以提供一個新的調度程序 。 您可以自己提供一個新的實現,但我認為最簡單的方法是使用ConcurrentExclusiveSchedulerPair
類來提供獨占調度程序,以按照提供值的相同順序處理source
。
你的代碼可能是這樣的:
var taskSchedulerPair = new ConcurrentExclusiveSchedulerPair();
var exclusiveTaskFactory = new TaskFactory(taskSchedulerPair.ExclusiveScheduler );
var exclusiveScheduler = new TaskPoolScheduler(exclusiveTaskFactory);
using (source.ObserveOn(exclusiveScheduler)...
更新 :
正如在其他人的帖子中所說,處理類似事件的正確方法是EventLoopScheduler
類。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.