【问题标题】:MVVM and asynchronous data accessMVVM 和异步数据访问
【发布时间】:2012-04-09 15:06:50
【问题描述】:

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

所以我想创建某种视图模型用来请求数据的“AssetManager”:

_someAssetManager.GetSomeSpecificAsset(assetId, OnGetSomeSpecificAssetCompleted)

请注意,这是一个异步调用。不过,我遇到了一些不同的问题。如果不同的视图模型(大致)同时请求相同的资产,我们如何确保我们不做不必要的工作并且它们都获得了我们可以绑定的相同对象?

不确定我的方法是否正确。我一直在浏览 Reactive Framework - 但我不知道如何在这种情况下使用它。关于我可以使用的框架/技术/模式的任何建议?这似乎是一种相当普遍的情况。

【问题讨论】:

    标签: c# wpf mvvm system.reactive caliburn.micro


    【解决方案1】:
    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;
    }
    

    【讨论】:

    • 现在第一个请求 id 5 的人得到了一个真正的请求,而其他快速连续请求的人都会听到同样的请求。
    • 更聪明的是,如果您没有真正从字典中删除该项目,它将永远需要再次请求资源。不过我没有这样做,因为它是内存泄漏(即字典变得越来越大),并且对某种 MRU 缓存策略进行编码会使该示例的说明性降低:)
    • 不错。你不能调用 OnCompleted() 吗?我在想,你也可以通过调用 OnNext(newValue) 来更新值 - 所以如果我订阅 GetAsset(id).Subscribe(x => DoWork());,只要资产在某处更新,就会调用 DoWork。跨度>
    • BehaviorSubject 在这种情况下似乎是我想要的。 ^^
    【解决方案2】:

    我在方法调用方面取得了成功,这些方法调用传入了一个在接收到数据时被调用的委托。您可以通过检查确定请求是否正在发生的布尔字段来分层要求让每个人都使用相同的数据(如果当前正在发生请求)。我会保留需要调用的委托的本地集合,以便在最终接收到数据时,包含要调用的委托的类可以迭代它们,传入新接收的数据。

    类似的东西:

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

    【讨论】:

    • 谢谢。尝试了非常相似的东西。我在尝试使用 Monitor.TryEnter(obj) 来确保线程安全时放弃了(从未让它工作)。也许我会再试一次。
    • @Pking 或许可以尝试lock(){ } 构造。我为这个问题打了一些代码,但它不完整..
    【解决方案3】:

    使用代理模式和事件,您可以提供同步和异步数据。让您的代理返回同步调用的缓存值,并在您接收异步数据时通过事件通知视图模型。代理还可以设计为跟踪数据请求和限制服务器连接(例如“引用计数”调用、请求的数据/接收的数据标志等)

    【讨论】:

    • 有趣,我去看看。
    【解决方案4】:

    我会这样设置你AssetManager

    public interface IAssetManager
    {
        IObservable<IAsset> GetSomeSpecificAsset(int assetId);
    }
    

    在内部,您需要返回一个异步填充的 Subject&lt;IAsset&gt;。做对了,每次调用GetSomeSpecificAsset,您只需一次调用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-10
      • 1970-01-01
      • 1970-01-01
      • 2017-11-01
      • 2023-04-02
      • 2019-10-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多