![](/img/trans.png)
[英]Learning Rx: How can I parse an observable sequence of characters into an observable sequence of strings?
[英]How can I await that everything is done in a Rx observable sequence after unsubscribe?
介紹
在我的WPF C#.NET應用程序中,我使用反應性擴展(Rx)訂閱事件,而且我經常不得不從數據庫中重新加載某些內容以獲取更新UI所需的值,因為事件對象通常僅包含ID和一些元數據。
我使用Rx調度在后台加載數據並更新調度程序上的UI。 我在Rx序列中混合“ Task.Run”時遇到了一些不好的經驗(當使用“ SelectMany”時,將不再保證順序,並且很難在UnitTests中控制調度)。 另請參閱: 在反應性管道中執行TPL代碼並通過測試調度程序控制執行
我的問題
如果我關閉我的應用程序(或關閉選項卡),我想取消訂閱,然后等待DB調用(從Rx“ Select”調用),該調用在“ subscription.Dispose”之后仍然可以運行。 到目前為止,我還沒有找到任何好的工具或簡便的方法來做到這一點。
問題
是否有任何框架支持來等待仍在Rx鏈中運行的所有內容 ?
如果沒有,您對如何制作易於使用的實用程序有什么好主意?
有沒有其他好的方法可以達到相同的目的?
例
public async Task AwaitEverythingInARxChain()
{
// In real life this is a hot observable event sequence which never completes
IObservable<int> eventSource = Enumerable.Range(1, int.MaxValue).ToObservable();
IDisposable subscription = eventSource
// Load data in the background
.ObserveOn(Scheduler.Default)
.Select(id => LoadFromDatabase(id))
// Update UI on the dispatcher
.ObserveOn(DispatcherScheduler.Current)
.SubscribeOn(Scheduler.Default) // In real life the source produces the event values on a background thread.
.Subscribe(loadedData => UpdateUi(loadedData));
Thread.Sleep(TimeSpan.FromSeconds(10));
// In real life I want to cancel (unsubscribe) here because the user has closed the Application or closed the tab and return a task which completes when everything is done.
// Unsubscribe just guarantees that no "OnNext" is called anymore, but it doesn't wait until all operations in the sequence are finished (for example "LoadFromDatabase(id)" can still be runnig here.
subscription.Dispose();
await ?; // I need to await here, so that i can be sure that no "LoadFromDatabase(id)" is running anymore.
ShutDownDatabase();
}
我已經嘗試過(但沒有用)的內容
更新:具有控制台輸出和TakeUntil的示例
public async Task Main()
{
Observable
.Timer(TimeSpan.FromSeconds(5.0))
.Subscribe(x =>
{
Console.WriteLine("Cancel started");
_shuttingDown.OnNext(Unit.Default);
});
await AwaitEverythingInARxChain();
Console.WriteLine("Cancel finished");
ShutDownDatabase();
Thread.Sleep(TimeSpan.FromSeconds(3));
}
private Subject<Unit> _shuttingDown = new Subject<Unit>();
public async Task AwaitEverythingInARxChain()
{
IObservable<int> eventSource = Observable.Range(0, 10);
await eventSource
.ObserveOn(Scheduler.Default)
.Select(id => LoadFromDatabase(id))
.ObserveOn(Scheduler.Default)
.TakeUntil(_shuttingDown)
.Do(loadedData => UpdateUi(loadedData));
}
public int LoadFromDatabase(int x)
{
Console.WriteLine("Start LoadFromDatabase: " + x);
Thread.Sleep(1000);
Console.WriteLine("Finished LoadFromDatabase: " + x);
return x;
}
public void UpdateUi(int x)
{
Console.WriteLine("UpdateUi: " + x);
}
public void ShutDownDatabase()
{
Console.WriteLine("ShutDownDatabase");
}
輸出(實際):
Start LoadFromDatabase: 0
Finished LoadFromDatabase: 0
Start LoadFromDatabase: 1
UpdateUi: 0
Finished LoadFromDatabase: 1
Start LoadFromDatabase: 2
UpdateUi: 1
Finished LoadFromDatabase: 2
Start LoadFromDatabase: 3
UpdateUi: 2
Finished LoadFromDatabase: 3
Start LoadFromDatabase: 4
UpdateUi: 3
Cancel started
Cancel finished
ShutDownDatabase
Finished LoadFromDatabase: 4
Start LoadFromDatabase: 5
Finished LoadFromDatabase: 5
Start LoadFromDatabase: 6
Finished LoadFromDatabase: 6
Start LoadFromDatabase: 7
預期:我想保證以下是最后的輸出:
Cancel finished
ShutDownDatabase
這比您想象的要容易。 您可以await
觀察。 因此,只需執行以下操作:
public async Task AwaitEverythingInARxChain()
{
IObservable<int> eventSource = Enumerable.Range(1, 10).ToObservable();
await eventSource
.ObserveOn(Scheduler.Default)
.Select(id => LoadFromDatabase(id))
.ObserveOn(DispatcherScheduler.Current)
.Do(loadedData => UpdateUi(loadedData), () => ShutDownDatabase());
}
在您的方法中使用Console.WriteLine
操作,並在db調用中休眠一個小線程以模擬網絡延遲,我得到以下輸出:
LoadFromDatabase: 1 LoadFromDatabase: 2 UpdateUi: 1 LoadFromDatabase: 3 UpdateUi: 2 LoadFromDatabase: 4 UpdateUi: 3 LoadFromDatabase: 5 UpdateUi: 4 LoadFromDatabase: 6 UpdateUi: 5 LoadFromDatabase: 7 UpdateUi: 6 LoadFromDatabase: 8 UpdateUi: 7 LoadFromDatabase: 9 UpdateUi: 8 LoadFromDatabase: 10 UpdateUi: 9 UpdateUi: 10 ShutDownDatabase
如果需要結束查詢,只需創建一個shuttingDown
主題:
private Subject<Unit> _shuttingDown = new Subject<Unit>();
...然后像這樣修改查詢:
await eventSource
.ObserveOn(Scheduler.Default)
.Select(id => LoadFromDatabase(id))
.ObserveOn(DispatcherScheduler.Current)
.Do(
loadedData => UpdateUi(loadedData),
() => ShutDownDatabase())
.TakeUntil(_shuttingDown);
您只需要發出_shuttingDown.OnNext(Unit.Default);
取消訂閱可觀察者。
這是我完整的工作測試代碼:
async Task Main()
{
Observable
.Timer(TimeSpan.FromSeconds(5.0))
.Subscribe(x => _shuttingDown.OnNext(Unit.Default));
await AwaitEverythingInARxChain();
}
private Subject<Unit> _shuttingDown = new Subject<Unit>();
public async Task AwaitEverythingInARxChain()
{
IObservable<int> eventSource = Observable.Range(0, 10);
await eventSource
.ObserveOn(Scheduler.Default)
.Select(id => LoadFromDatabase(id))
.ObserveOn(DispatcherScheduler.Current)
.Finally(() => ShutDownDatabase())
.TakeUntil(_shuttingDown)
.Do(loadedData => UpdateUi(loadedData));
}
public int LoadFromDatabase(int x)
{
Console.WriteLine("LoadFromDatabase: " + x);
Thread.Sleep(1000);
return x;
}
public void UpdateUi(int x)
{
Console.WriteLine("UpdateUi: " + x);
}
public void ShutDownDatabase()
{
Console.WriteLine("ShutDownDatabase");
}
我得到以下輸出:
LoadFromDatabase: 0 LoadFromDatabase: 1 UpdateUi: 0 LoadFromDatabase: 2 UpdateUi: 1 LoadFromDatabase: 3 UpdateUi: 2 LoadFromDatabase: 4 UpdateUi: 3 LoadFromDatabase: 5 UpdateUi: 4 ShutDownDatabase
請注意,可觀察對象嘗試在10秒內生成10個值,但是它被OnNext
縮短了。
我終於找到了解決方案。 您可以使用TakeWhile實現它。 TakeUntil不起作用,因為當第二個可觀察序列產生第一個值時,主要可觀察序列立即完成。
這是工作解決方案的示例:
public async Task Main_Solution()
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
Observable
.Timer(TimeSpan.FromSeconds(4))
.Subscribe(x =>
{
Console.WriteLine("Cancel startedthread='{0}'", Thread.CurrentThread.ManagedThreadId);
cancellationTokenSource.Cancel();
});
await AwaitEverythingInARxChain(cancellationTokenSource.Token);
Console.WriteLine("Cancel finished thread='{0}'", Thread.CurrentThread.ManagedThreadId);
ShutDownDatabase();
Thread.Sleep(TimeSpan.FromSeconds(10));
}
public async Task AwaitEverythingInARxChain(CancellationToken token)
{
IObservable<int> eventSource = Observable.Range(0, 10);
await eventSource
.ObserveOn(Scheduler.Default)
.Select(id => LoadFromDatabase(id))
.TakeWhile(_ => !token.IsCancellationRequested)
.ObserveOn(Scheduler.Default) // Dispatcher in real life
.Do(loadedData => UpdateUi(loadedData)).LastOrDefaultAsync();
}
public int LoadFromDatabase(int x)
{
Console.WriteLine("Start LoadFromDatabase: {0} thread='{1}'", x, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(TimeSpan.FromSeconds(3));
Console.WriteLine("Finished LoadFromDatabase: {0} thread='{1}'", x, Thread.CurrentThread.ManagedThreadId);
return x;
}
public void UpdateUi(int x)
{
Console.WriteLine("UpdateUi: '{0}' thread='{1}'", x, Thread.CurrentThread.ManagedThreadId);
}
public void ShutDownDatabase()
{
Console.WriteLine("ShutDownDatabase thread='{0}'", Thread.CurrentThread.ManagedThreadId);
}
並輸出:
Start LoadFromDatabase: 0 thread='9'
Finished LoadFromDatabase: 0 thread='9'
Start LoadFromDatabase: 1 thread='9'
UpdateUi: '0' thread='10'
Cancel startedthread='4'
Finished LoadFromDatabase: 1 thread='9'
Cancel finished thread='10'
ShutDownDatabase thread='10'
請注意,“ ShutDownDatabase”是最后的輸出(按預期)。 它等待直到“ LoadFromDatabase”完成第二個值,即使其產生的值未得到進一步處理也是如此。 這正是我想要的。
您需要等待一些事情。 您不能等待訂閱處置。 最簡單的方法是將您的處置邏輯變成可觀察的一部分:
var observable = eventSource
// Load data in the background
.ObserveOn(Scheduler.Default)
.Select(id => LoadFromDatabase(id))
.TakeUntil(Observable.Timer(TimeSpan.FromSeconds(10))) //This replaces your Thread.Sleep call
.Publish()
.RefCount();
var subscription = observable.ObserveOn(DispatcherScheduler.Current)
.Subscribe(loadedData => UpdateUi(loadedData));
//do whatever you want here.
await observable.LastOrDefault();
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.