C# MVVM与异步数据访问

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

因此,我有一个使用MVVM模式(Caliburn.Micro)的WPF应用程序。我将视图和视图模型连接起来,基本上缺少的是数据。数据将“按需”从WCF服务、本地存储或内存/缓存中检索-原因是允许脱机模式并避免不必要的服务器通信。另一个要求是异步检索数据,以便不阻塞UI线程

所以我想创建一种viewmodels用来请求数据的“AssetManager”:

_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;
}