代码之家  ›  专栏  ›  技术社区  ›  Jeremy Likness

在Silverlight中如何使用反应式扩展(Rx)组织这些调用?

  •  7
  • Jeremy Likness  · 技术社区  · 14 年前

    void IService.Query(Action<IEnumerable<Widget>,Exception> callback);
    void IService.Load(Action<Widget,Exception> callback); 
    

    考虑到这一点,下面是视图模型的草图:

    public class ViewModel : BaseViewModel
    {
       public ViewModel()
       {
          Widgets = new ObservableCollection<Widget>();
    
          WidgetService.Query((widgets,exception) =>
          {
              if (exception != null) 
              {
                  throw exception;
              }
    
              Widgets.Clear();
    
              foreach(var widget in widgets)
              {
                 Widgets.Add(widget);
              }
    
              WidgetService.Load((defaultWidget,ex) =>
              {
                 if (ex != null)
                 {
                     throw ex;
                 }
                 if (defaultWidget != null)
                 {
                    CurrentWidget = defaultWidget;
                 }
              }
          });
       }
    
       public IService WidgetService { get; set; } // assume this is wired up
    
       public ObservableCollection<Widget> Widgets { get; private set; }
    
       private Widget _currentWidget; 
    
       public Widget CurrentWidget 
       {
          get { return _currentWidget; }
          set 
          {
             _currentWidget = value; 
             RaisePropertyChanged(()=>CurrentWidget);
          }
       }
    }
    

    我想做的是简化依次调用query和default的工作流程。也许最好的方法是用lambda表达式嵌套,正如我所展示的,但是我认为使用Rx可能有一种更优雅的方法。我不想为了Rx而使用Rx,但是如果它能让我组织上面的逻辑,这样在方法中更容易阅读/维护,我会利用它。理想情况下,类似于:

    Observable.Create(
       ()=>firstAction(), 
       ()=>secondAction())
    .Subscribe(action=>action(),error=>{ throw error; }); 
    

    使用power线程库,我可以执行以下操作:

    Service.Query(list=>{result=list};
    yield return 1;
    ProcessList(result);
    Service.Query(widget=>{defaultWidget=widget};
    yield return 1;
    CurrentWidget = defaultWidget;
    

    任何类似的事情对我来说都是有意义的。

    所以问题的实质是:我是在尝试将一个方形的peg放入一个圆形的孔中,还是有办法使用Rx重新定义嵌套的异步调用?

    2 回复  |  直到 14 年前
        1
  •  3
  •   desco    14 年前

    选择多个

            WidgetService.Query()
                .SelectMany(
                    widgets =>
                    {
                        Widgets.Clear();
                        foreach (var w in widgets)
                        {
                            Widgets.Add(w);
                        }
    
                        return WidgetService.Load();
                    }
                )
                .Do(
                    defaultWidget =>
                    {
                        if (defaultWidget != null)
                            Default = defaultWidget;
                    }
                )
                .Subscribe(
                    _ => { },
                    e => { throw e; }
                );
    

    不过,IMO F#Async看起来更清楚(在示例中,我假设服务的方法返回Async>和异步)。请注意,示例没有考虑线程正在修改的数据字段,在实际代码中,您应该注意以下几点:

        let load = async {
                let! widgets = WidgetService.Query()
    
                Widgets.Clear()
                for w in widgets do
                    Widgets.Add(w)
    
                let! defaultWidget = WidgetService.Load()
                if defaultWidget <> null then
                    Default <- defaultWidget
    
                return ()
            }
    
        Async.StartWithContinuations(
            load, 
            ignore, // success continuation - ignore result
            raise,  // error continuation - reraise exception
            ignore  // cancellation continuation - ignore
            )
    

    事实上,您在问题中提到的迭代器可以使用以下技术:

        private IEnumerable<IObservable<object>> Intialize()
        {
            var widgetsList = WidgetService.Query().Start();
            yield return widgetsList;
    
            Widgets.Clear();
            foreach (var w in widgetsList[0])
            {
                Widgets.Add(w);
            }
    
            var defaultWidgetList = WidgetService.Load().Start();
            yield return defaultWidgetList;
    
            if (defaultWidgetList[0] != null)
                Default = defaultWidgetList[0];
        }
    
        Observable
            .Iterate(Intialize)
            .Subscribe(
            _ => { },
            ex => { throw ex; }
            );
    
        2
  •  1
  •   Ana Betts    14 年前

    你也可以用 ReactiveXaml ,但是由于CurrentWidget和Widget都是可变的,所以不能使其保持干净(有一个类称为 ObservableAsPropertyHelper

    public class ViewModel
    {
        public ViewModel()
        {
            // These return a Func that wraps an async call in an IObservable<T>
            // that always yields only one item (the result of the call)
            var QueryAsObservable = Observable.FromAsyncCommand<IEnumerable<Widget>>(WebService.BeginQuery, WebService.EndQuery);
            var LoadAsObservable = Observable.FromAsyncCommand<Widget>(WebService.BeginLoad, WebService.EndLoad);
    
            // Create a new command 
            QueryAndLoad = new ReactiveAsyncCommand();
    
            // QueryAndLoad fires every time someone calls ICommand.Execute
            // The .Do is the hacky part, for sync calls it's hidden by RegisterAsyncFunction
            var async_results = QueryAndLoad.SelectMany(_ => QueryAsObservable())
                                            .Do(_ => DoTranslate.AsyncCompletedNotification.OnNext(new Unit()));
    
            // Query up the Widgets 
            async_results.Subscribe(x => x.Run(Widgets.Add));
    
            // Now execute the Load
            async_results.SelectMany(_ => LoadAsObservable())
                         .Subscribe(x => CurrentWidget = x);
    
            QueryAndLoad.Execute();
        }
    
        public ReactiveAsyncCommand QueryAndLoad {get; private set; }
    
        public ObservableCollection<Widget> Widgets {get; private set; }
    
        public Widget CurrentWidget {get; set; }
    }