使用Rx轮询网站

时间:2012-05-23 06:39:06

标签: c# .net system.reactive

试图让我的头围绕Rx

我使用Rx每2秒轮询一次网站

var results = new List<MyDTO>();
var cx = new WebserviceAPI( ... );
var callback = cx.GetDataAsync().Subscribe(rs => { results.AddRange(rs); });
var poller = Observable.Interval(TimeSpan.FromSeconds(2)).Subscribe( _ => { cx.StartGetDataAsync(); });

(webservice API公开了一个getItemsAsync / getItemsCompleted事件处理程序类型机制,我从中创建了一个observable)。

当网站返回时,我正在将响应的“业务部分”解压缩到IEnumerable of DTO中

public IObservable<IEnumerable<MyDTO>> GetDataAsync()
{
    var o = Observable.FromEventPattern<getItemsCompletedEventHandler,getItemsCompletedEventArgs>(
        h => _webService.getItemsCompleted += h,
        h => _webService.getItemsCompleted -= h);

    return o.Select(c=> from itm in c.EventArgs.Result.ItemList
                        select new MyDTO()
                        {
                           ...
                        });
}

我的理由是,鉴于所有数据都只是在字符串中,因此只需将其打包到IEnumerable中就可以了......但现在我不确定这是否正确!

如果网站响应时间超过2秒,我发现MSTest正在崩溃。调试时,生成的错误是

  

“异步处理期间出错。独特状态   对象是多个异步同时操作所必需的   杰出的“

内部异常

  

“项目已添加。键入字典:'System.Object'键   被添加:'System.Object'“

我认为这个问题是重入的问题,因为下一个调用正在启动并在前一个调用完成填充数据之前返回数据。

所以我不确定是否

  1. 我把事情放在一起非常正确
  2. 我应该以某种方式限制连接,以避免重新入侵。
  3. 我应该使用不同的中间数据结构(或机制) 而不是IEnumerable
  4. 我很感激一些指导。

    编辑1: 所以我更改了Web调用以包含一个唯一的状态对象

    public void StartGetDataAsync()
    {
        ...
        //  was: _webService.getItemsAsync(request);
        _webService.getItemsAsync(request, Guid.NewGuid());
    }
    

    并使其成功。但我仍然不确定这是否是正确的做法

    EDIT 2 - Web服务sigs: 我正在使用webServiceApi类包装的soap web服务。创建的references.cs包含以下方法

    public void getItemsAsync(GetItemsReq request, object userState) 
    {
        if ((this.getItemsOperationCompleted == null)) 
        {
            this.getItemsOperationCompleted = new System.Threading.SendOrPostCallback(this.OngetItemsOperationCompleted);
        }
        this.InvokeAsync("getItems", new object[] {
                        request}, this.getItemsOperationCompleted, userState);
    }
    
    private System.Threading.SendOrPostCallback getItemsOperationCompleted;
    
    public event getItemsCompletedEventHandler getItemsCompleted;
    
    public delegate void getItemsCompletedEventHandler(object sender, getItemsCompletedEventArgs e);
    
    public partial class getItemsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs 
    {
        ...
    }
    
    private void OngetItemsOperationCompleted(object arg) 
    {
        if ((this.getItemsCompleted != null)) 
        {
            System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg));
            this.getItemsCompleted(this, new getItemsCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState));
        }
     }
    

    可能给你太多(或错过了一些东西)!

    THX

2 个答案:

答案 0 :(得分:1)

我想我有一个不错的起点。

基本上我认为你需要抽象出Web服务的复杂性并创建一个很好的清理函数来获得结果。

尝试这样的事情:

Func<GetItemsReq, IObservable<getItemsCompletedEventArgs>> fetch =
    rq =>
        Observable.Create<getItemsCompletedEventArgs>(o =>
        {
            var cx = new WebserviceAPI(/* ... */);
            var state = new object();
            var res =
                Observable
                    .FromEventPattern<
                        getItemsCompletedEventHandler,
                        getItemsCompletedEventArgs>(
                        h => cx.getItemsCompleted += h,
                        h => cx.getItemsCompleted -= h)
                    .Where(x => x.EventArgs.UserState == state)
                    .Take(1)
                    .Select(x => x.EventArgs);
            var subscription = res.Subscribe(o);
            cx.getItemsAsync(rq, state);
            return subscription;
        });

就个人而言,我会更进一步定义一个返回类型,比如GetItemsReq,它不包含用户状态对象,但与getItemsCompletedEventArgs基本相同。

然后,您应该可以使用Observable.Interval创建所需的轮询。

如果您的Web服务实现了IDisposable,那么您应该在上述函数中添加Observable.Using调用,以便在Web服务完成后正确处理它。

如果有帮助,请告诉我。

答案 1 :(得分:1)

Enigmativity有一个很好的解决方案。

          var state = new object();

          cx.getItemsAsync(rq, state);

这些陈述是解决错误的关键。

每次调用Async方法时,都需要传递一个唯一的对象(除非当然一次只运行一个Async方法,这是非常不可能的)。我抓了几个小时,直到我发现你可以传递一个对象参数。如果没有参数,它将看到多个方法作为同一个调用,并为您提供“已添加项目。键入字典:'System.Object'键被添加:'System.Object'”消息。