如何继续重试反应方法直到成功?

时间:2011-10-17 18:18:05

标签: c# asynchronous webclient system.reactive

这是我对WebClient的异步下载反应式扩展。 在操作成功之前,一次又一次地回忆“DownloadStringAsync”的最佳方法是什么?

这样的东西,但是以反应的方式:

while (true)
{
  var result = DownloadStringAsync();
  if (result)
  {
    return;
  }
}

我的代码:

[Serializable]
public class WebClientException : Exception
{
    public WebClientResponse Response { get; set; }

    public WebClientException()
    {
    }

    public WebClientException(string message)
        : base(message)
    {
    }

    public WebClientException(string message, Exception innerException)
        : base(message, innerException)
    {
    }

    protected WebClientException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
    }
}

public class WebClientResponse
{
    public WebHeaderCollection Headers { get; set; }
    public HttpStatusCode StatusCode { get; set; }
    public string Result { get; set; }
    public WebException Exception { get; set; }
}

public static IObservable<WebClientResponse> DownloadStringAsync(this WebClient webClient, Uri address, WebHeaderCollection requestHeaders)
{
    var asyncResult =
        Observable.FromEventPattern<DownloadStringCompletedEventHandler, DownloadStringCompletedEventArgs>
            (ev => webClient.DownloadStringCompleted += ev, ev => webClient.DownloadStringCompleted -= ev)
            .ObserveOn(Scheduler.TaskPool)
            .Select(o =>
                        {
                            var ex = o.EventArgs.Error as WebException;

                            if (ex == null)
                            {
                                var wc = (WebClient) o.Sender;

                                return new WebClientResponse {Headers = wc.ResponseHeaders, Result = o.EventArgs.Result};
                            }

                            var wcr = new WebClientResponse {Exception = ex};

                            var r = ex.Response as HttpWebResponse;
                            if (r != null)
                            {
                                wcr.Headers = r.Headers;
                                wcr.StatusCode = r.StatusCode;

                                var s = r.GetResponseStream();
                                if (s != null)
                                {
                                    using (TextReader tr = new StreamReader(s))
                                    {
                                        wcr.Result = tr.ReadToEnd();
                                    }
                                }
                            }

                            throw new WebClientException {Response = wcr};
                        })
            .Take(1);

    if (requestHeaders != null)
    {
        foreach (var key in requestHeaders.AllKeys)
        {
            webClient.Headers.Add(key, requestHeaders[key]);
        }
    }

    webClient.DownloadStringAsync(address);

    return asyncResult;
}

3 个答案:

答案 0 :(得分:1)

您的方法生成一个热的observable,这意味着它在返回时已经开始加载,并且每个新订阅都不会向Web服务器创建新请求。你需要将你的方法包装在另一个中并使用Observable.Create(为了创建一个冷的observable,它会在每次订阅时创建一个新的请求):

public static IObservable<WebClientResponse> DownloadStringAsync(this WebClient webClient, Uri address, WebHeaderCollection requestHeaders)
{
    return Observable
        .Create(observer => 
        {
            DownloadStringAsyncImpl(webClient, address, requestHeaders)
                .Subscribe(observer);
            return () => { webClient.CancelAsync(); };
        });
}

这里,DownloadStringAsyncImpl是您之前的DownloadStringAsync实现,而公共方法已被替换。

现在您可以重试异步方法,直到成功,如下所示:

myWebClient
    .DownloadStringAsync( /* args... */)
    .Retry()
    .Subscribe(result => { 
         /* now I've got a result! */
    });

答案 1 :(得分:1)

我认为你至少有一个不错的“这里是一些代码”的答案,所以我将专注于更普遍的手持。

我要看的第一件事是design guidelines for Rx。这是一个简短的(34页)PDF文档,有助于将范式从拉“订阅”更改为推送,或从IEnumerable转移到IObservable。

如果您想进一步了解,.NETJavaScript都有PDF HOL(动手练习)。您可以在Rx页面上找到其他资源(start here)。

答案 2 :(得分:0)

如果是异步功能。进行重复检查意味着您将其转换为同步函数调用。这是你真正想做的事吗?

您可以让一个专用线程调用此异步函数,并在调用此函数后阻止自身。创建此线程时,传递一个应该在异步函数返回后调用的委托。完成后,使用错误代码调用代理。

希望这能回答你的问题。