如果您使用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请求的事情,我需要能够取消(而不是忽略)部分/全部以节省网络流量。
答案 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);
}