所以我有一个使用MVVM模式(Caliburn.Micro)的WPF应用程序。我把视图和视图模型连接起来,基本上缺少的是数据。数据将从 WCF 服务、本地存储或内存/缓存中"按需"检索 - 原因是允许脱机模式并避免不必要的服务器通信。另一个要求是异步检索数据,以便不会阻止 UI 线程。
所以我在考虑创建某种视图模型用来请求数据的"资源管理器":
_someAssetManager.GetSomeSpecificAsset(assetId, OnGetSomeSpecificAssetCompleted)
请注意,这是一个异步调用。不过,我遇到了几个不同的问题。如果不同的视图模型(大致)同时请求相同的资产,我们如何确保我们不会做不必要的工作,并且它们都获得我们可以绑定的相同对象?
不确定我是否有正确的方法。我一直在看反应式框架 - 但我不知道如何在这种情况下使用它。关于我可以使用的框架/技术/模式的任何建议?这似乎是一个相当普遍的情况。
Dictionary<int, IObservable<IAsset>> inflightRequests;
public IObservable<IAsset> GetSomeAsset(int id)
{
// People who ask for an inflight request just get the
// existing one
lock(inflightRequests) {
if inflightRequests.ContainsKey(id) {
return inflightRequests[id];
}
}
// Create a new IObservable and put in the dictionary
lock(inflightRequests) { inflightRequests[id] = ret; }
// Actually do the request and "play it" onto the Subject.
var ret = new AsyncSubject<IAsset>();
GetSomeAssetForReals(id, result => {
ret.OnNext(id);
ret.OnCompleted();
// We're not inflight anymore, remove the item
lock(inflightRequests) { inflightRequests.Remove(id); }
})
return ret;
}
我在方法调用方面取得了成功,这些调用传入在收到数据时调用的委托。 您可以通过检查确定请求是否正在发生的布尔字段来分层要求,使每个人都使用相同的数据(如果当前正在发生请求)。 我会保留一个需要调用的委托的本地集合,以便在最终收到数据时,包含要调用的委托的类可以迭代它们,传入新接收的数据。
大致如下:
public interface IViewModelDataLoader{
void LoadData(AssignData callback);
}
public delegate void AssignData(IEnumerable<DataObject> results);
然后,实际实现此接口的类可以对数据完成时通知谁进行运行统计(假设是单例模型):
public class ViewModelDataLoader : IViewModelDataLoader{
private IList<AssignData> callbacksToCall;
private bool isLoading;
public void LoadData(AssignData callback){
callbacksToCall.add(callback);
if (isLoading) { return; }
// Do some long running code here
var data = something;
// Now iterate the list
foreach(var item in callbacksToCall){
item(data);
}
isLoading = false;
}
}
使用代理模式和事件,您可以同时提供同步和异步数据。让您的代理返回同步调用的缓存值,并在接收异步数据时通过事件通知视图模型。代理还可以设计为跟踪数据请求和限制服务器连接(例如"引用计数"调用、请求的数据/接收数据标志等)
我会像这样设置你AssetManager
:
public interface IAssetManager
{
IObservable<IAsset> GetSomeSpecificAsset(int assetId);
}
在内部,您需要返回异步填充的Subject<IAsset>
。做对了,每次调用只有一个呼叫GetSomeSpecificAsset
。