C# MVVM与异步数据访问
因此,我有一个使用MVVM模式(Caliburn.Micro)的WPF应用程序。我将视图和视图模型连接起来,基本上缺少的是数据。数据将“按需”从WCF服务、本地存储或内存/缓存中检索-原因是允许脱机模式并避免不必要的服务器通信。另一个要求是异步检索数据,以便不阻塞UI线程 所以我想创建一种viewmodels用来请求数据的“AssetManager”:C# MVVM与异步数据访问,c#,wpf,mvvm,system.reactive,caliburn.micro,C#,Wpf,Mvvm,System.reactive,Caliburn.micro,因此,我有一个使用MVVM模式(Caliburn.Micro)的WPF应用程序。我将视图和视图模型连接起来,基本上缺少的是数据。数据将“按需”从WCF服务、本地存储或内存/缓存中检索-原因是允许脱机模式并避免不必要的服务器通信。另一个要求是异步检索数据,以便不阻塞UI线程 所以我想创建一种viewmodels用来请求数据的“AssetManager”: _someAssetManager.GetSomeSpecificAsset(assetId, OnGetSomeSpecificAssetCo
_someAssetManager.GetSomeSpecificAsset(assetId, OnGetSomeSpecificAssetCompleted)
请注意,这是一个异步调用。不过我遇到了一些不同的问题。如果不同的视图模型(大致)在同一时间请求相同的资产,我们如何确保我们不做不必要的工作,并且它们都得到我们可以绑定的相同对象
我不确定我的方法是否正确。我已经浏览了一些被动框架,但我不知道如何在这个场景中使用它。对我可以使用的框架/技术/模式有什么建议吗?这似乎是一种相当常见的情况。我成功地使用了方法调用,这些方法调用传入一个委托,该委托在接收数据时被调用。您可以通过检查一个确定请求是否发生的布尔字段来分层要求,使每个人都具有相同的数据(如果当前正在发生请求)。我将保留需要调用的委托的本地集合,以便在最终接收到数据时,包含要调用的委托的类可以迭代这些委托,传递新接收的数据 大致如下:
public interface IViewModelDataLoader{
void LoadData(AssignData callback);
}
public delegate void AssignData(IEnumerable<DataObject> results);
公共接口IViewModelDataLoader{
void LoadData(AssignData回调);
}
公共委托数据(IEnumerable results);
然后,实际实现此接口的类可以在数据完成时(假设为单例模型)对要通知的对象保持一个运行计数:
公共类ViewModelDataLoader:IViewModelDataLoader{
私人IList Callbackscoll;
私有布尔孤岛加载;
公共void LoadData(AssignData回调){
callbackscoll.add(回调);
if(isload){return;}
//在这里执行一些长时间运行的代码
var数据=某物;
//现在迭代列表
foreach(callbackscoll中的变量项){
项目(数据);
}
isLoading=false;
}
}
使用代理模式和事件,您可以提供同步和异步数据。让代理为同步调用返回缓存值,并在接收异步数据时通过事件通知视图模型。代理还可以设计为跟踪数据请求和限制服务器连接(例如“引用计数”调用、请求的数据/接收的数据标志等)我会将您设置为AssetManager
:
public interface IAssetManager
{
IObservable<IAsset> GetSomeSpecificAsset(int assetId);
}
公共接口IAssetManager
{
IObservable GetSomeSpecificAsset(int assetId);
}
在内部,您需要返回一个异步填充的主题
。如果做得好,那么每次调用GetSomeSpecificAsset
Dictionary inflightRequests时,您只有一个调用;
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;
}
公共IObservable GetSomeAsset(int id)
{
//请求机上请求的人只需获得
//现有的
锁定(请求){
如果inflightRequests.ContainsKey(id){
返回请求[id];
}
}
//创建一个新的IObservable并放入字典
锁(inflightRequests){inflightRequests[id]=ret;}
//实际执行请求并“播放”主题。
var ret=new AsyncSubject();
GetSomeAssetForReals(id,结果=>{
ret.OnNext(id);
ret.OnCompleted();
//我们不在飞机上了,把东西拿开
锁定(inflightRequests){inflightRequests.Remove(id);}
})
返回ret;
}
谢谢。我试过类似的东西。当我试图使用Monitor.TryEnter(obj)来确保线程安全时,我放弃了(从未让它工作)。也许我会再试一次。@Pking也许会尝试lock(){}
construct。。我已经为这个问题输入了一些代码,但它不完整..Interresting,我会检查它。现在,第一个要求id 5的人得到了一个真正的请求,所有其他快速提出请求的人都会听到相同的请求。更聪明的是,如果你不真的从字典中删除该项,它再也不需要请求资源了。我没有这样做,因为这是一个内存泄漏(也就是说,字典越来越大),编码某种MRU缓存策略会使这个示例不那么说明问题:)很好。您不能调用OnCompleted()吗?我的想法是,您也可以通过调用OnNext(newValue)来更新这些值——因此,如果我订阅GetAsset(id).subscribe(x=>DoWork());,每当资产在某处更新时,就会调用DoWork。在这种情况下,BehaviorSubject似乎就是我想要的^^
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;
}