Reactive Extensions清理

时间:2010-12-22 03:30:56

标签: c# linq httpwebrequest system.reactive

如果您使用rx进行长链调用,例如:

var responses = collectionOfHttpRequests.ToObservable()
.FromAsyncPattern(req.BeginGetResponse, req.EndGetResponse)
.Select(res => res.GetResponseBodyString()) // Extension method to get the body of the request
.Subscribe();

然后在操作完成之前调用一个dispose,http请求是否会被取消,关闭和正确处理,或者我是否必须以某种方式从方法链中选择httprequests并单独处理它们?

我有一个可以同时发生多个http请求的事情,我需要能够取消(而不是忽略)部分/全部以节省网络流量。

2 个答案:

答案 0 :(得分:2)

当序列完成,错误或订阅被处置时,Rx操作员链将自行清理。但是,每个操作员只会清理他们希望清理的内容。例如,FromEvent将取消订阅该活动。

在您的情况下,Begin/End asynchronous pattern不支持取消,因此Rx无法取消。但是,您可以使用Finally来致电HttpWebRequest.Abort

var observableRequests = collectionOfHttpRequests.ToObservable();

var responses = observableRequests
    .SelectMany(req => 
        Observable.FromAsyncPattern(req.BeginGetResponse, req.EndGetResponse)()
    )
    .Select(resp => resp.GetResponseBodyString())
    .Finally(() =>
    {
        observableRequests
            .Subscribe(req => req.Abort());
    })
    .Subscribe();

答案 1 :(得分:2)

我不能承认Richard Szalay的解决方案是可以接受的。如果您启动100个请求并且第二个请求因服务器不可用错误而失败(例如),则将中止其余98个请求。第二个问题是UI将如何对这种可观察的反应?太伤心。

记住Rx Design Guidelines的第4.3章我希望通过Observable.Using()运算符表达WebRequest observable。但WebRequest不是一次性的!所以我定义了DisposableWebRequest:

public class DisposableWebRequest : WebRequest, IDisposable
{
    private static int _Counter = 0;

    private readonly WebRequest _request;
    private readonly int _index;

    private volatile bool _disposed = false;

    public DisposableWebRequest (string url)
    {
        this._request = WebRequest.Create(url);
        this._index = ++DisposableWebRequest._Counter;
    }

    public override IAsyncResult BeginGetResponse(AsyncCallback callback, object state)
    {
        return this._request.BeginGetResponse(callback, state);
    }

    public override WebResponse EndGetResponse(IAsyncResult asyncResult)
    {
        Trace.WriteLine(string.Format("EndGetResponse index {0} in thread {1}", this._index, Thread.CurrentThread.ManagedThreadId));
        Trace.Flush();
        if (!this._disposed)
        {
            return this._request.EndGetResponse(asyncResult);
        }
        else
        {
            return null;
        }
    }

    public override WebResponse GetResponse()
    {
        return this._request.GetResponse();
    }

    public override void Abort()
    {
        this._request.Abort();
    }

    public void Dispose()
    {
        if(!this._disposed)
        {
            this._disposed = true;

            Trace.WriteLine(string.Format("Disposed index {0} in thread {1}", this._index, Thread.CurrentThread.ManagedThreadId ));
            Trace.Flush();
            this.Abort();
        }
    }
}

然后我创建WPF窗口并在其上放置两个按钮(开始和停止)。

所以,让我们创建适当的请求可观察集合。 首先,定义URL的可观察创建函数:

        Func<IObservable<string>> createUrlObservable = () =>
            Observable
                .Return("http://yahoo.com")
                .Repeat(100)
                .OnStartup(() =>
                {
                    this._failed = 0;
                    this._successed = 0;
                });

在每个网址上我们应该创建webrequest obervable,所以:

        Func<string, IObservable<WebResponse>> createRequestObservable = 
            url => 
            Observable.Using(
                () => new DisposableWebRequest("http://yahoo.com"),
                r =>
                {
                    Trace.WriteLine("Queued " + url);
                    Trace.Flush();
                    return Observable
                        .FromAsyncPattern<WebResponse>(r.BeginGetResponse, r.EndGetResponse)();
                });

另外定义两个事件observable,它们对按钮“开始”/“停止”点击作出反应:

        var startMouseDown = Observable.FromEvent<RoutedEventArgs>(this.StartButton, "Click");
        var stopMouseDown = Observable.FromEvent<RoutedEventArgs>(this.StopButton, "Click");

所以砖块准备就绪,有时间组成它们(我在InitializeComponent()之后的视图构造函数中执行它):

        startMouseDown
            .SelectMany(down =>
                createUrlObservable()
                    .SelectMany(url => createRequestObservable(url)
                        .TakeUntil(stopMouseDown)
                        .Select(r => r.GetResponseStream())
                        .Do(s =>
                            {
                                using (var sr = new StreamReader(s))
                                {
                                    Trace.WriteLine(sr.ReadLine());
                                    Trace.Flush();
                                }

                            })
                        .Do(r => this._successed++)
                        .HandleError(e => this._failed++))
                        .ObserveOnDispatcher()
                        .Do(_ => this.RefresLabels(),
                            e => { },
                            () => this.RefresLabels())

                        )
            .Subscribe();

你可能想知道函数“HandleError()”。如果在EndGetResponse()调用中发生异常(我关闭网络连接以重现它)并且没有在请求observable中捕获 - 它将使startMouseDown observable崩溃。 HandleError以静默方式捕获异常,提供操作,而不是为下一个调用OnCompleted的观察者调用OnError:

public static class ObservableExtensions
{
    public static IObservable<TSource> HandleError<TSource>(this IObservable<TSource> source, Action<Exception> errorHandler)
    {
        return Observable.CreateWithDisposable<TSource>(observer =>
            {
                return source.Subscribe(observer.OnNext, 
                    e => 
                    { 
                        errorHandler(e);
                        //observer.OnError(e);
                        observer.OnCompleted();
                    },
                    observer.OnCompleted);
            });
    }
}

最后一个无法解释的地方是方法RefreshLabels,它更新了UI控件:

    private void RefresLabels()
    {
        this.SuccessedLabel.Content = string.Format("Successed {0}", this._successed);
        this.FailedLabel.Content = string.Format("Failed {0}", this._failed);
    }