【问题标题】:How to asynchronously make a web request without a callback如何在没有回调的情况下异步发出 Web 请求
【发布时间】:2014-03-29 14:33:03
【问题描述】:

我有一项服务可以与 Internet 上的第 3 方对话,如果我想扩大规模,我不希望我的线程在可以处理新请求时被阻塞等待响应。解决此问题的一种方法是使用 httpRequest.BeginGetResponse 方法并传入回调。这个问题是这个方法立即返回,我没有什么可以回馈给我的服务的调用者。一旦进入回调,我就无法做任何有用的事情。

我想要的是这样的

public string CallServiceNonBlocking(string url) {

   var httpRequest = (HttpWebRequest)WebRequest.Create(url);

   HttpResponse res = httpRequest.FetchNonBlocking(); //this does the work but my thread should  be returned to the asp.net thread pool to do other work rather than blocking here

   return res.GetValue(); 

}

请注意,我没有 .Net 4.5,只有 .NET 4 可供我使用。但作为参考,我可以看到两个版本的解决方案吗?

【问题讨论】:

  • 您的 CallServiceNonBlocking 必须在获取完成时采用完成处理程序/回调。您可以在 .NET 4.0 中使用 TPL。
  • 你能给我看看这个样子吗?
  • 异步冒泡到每个调用者。没有免费的午餐。
  • i don't want my threads to be blocked waiting for the response when they could be handling new requests 也许只是增加工作线程数?这是一个非常简单的解决方案。

标签: c# asp.net asynchronous .net-4.0 async-await


【解决方案1】:

使用 .NET 4.5 和 C# 5.0 编码 async/awaitvery easy

如果需要,您也可以使其与 .NET 4.0 / C# 4.0 异步工作。您需要从AsyncController 派生控制器并使用AsyncManager"Using an Asynchronous Controller in ASP.NET MVC"中描述了基本步骤。

因此,您的代码可能如下所示(未经测试):

static public Task<WebResponse> GetResponseTapAsync(this WebRequest request)
{
    return Task.Factory.FromAsync(
         (asyncCallback, state) =>
             request.BeginGetResponse(asyncCallback, state),
         (asyncResult) =>
             request.EndGetResponse(asyncResult), null);
}

// ...

public class YourController : AsyncController
{
    public void YourMethodAsyc(string url)
    {
        AsyncManager.OutstandingOperations.Increment();

        var request = (HttpWebRequest)WebRequest.Create(url);
        request.GetResponseTapAsync().ContinueWith(responseTask =>
        {
            try
            {
                var stream = responseTask.Result.GetResponseStream();
                using (var streamReader = new StreamReader(stream))
                {
                    // still blocking here, see notes below
                    var data = streamReader.ReadToEnd();

                    AsyncManager.Parameters["data"] = data;
                }
            }
            finally
            {
                AsyncManager.OutstandingOperations.Decrement();
            }
        }, TaskScheduler.FromCurrentSynchronizationContext());
    }

    public ActionResult YourMethodCompleted(string data)
    {
        return View("Data", new ViewModel
        {
            Data = data
        });
    }
}

您可以更进一步并实现ReadToEndAsync(因为它在.NET 4.0 中不存在),但您将无法使用using。更多详情请查看this

理想情况下,如果您需要使用 .NET 4.0 以 ASP.NET MVC 为目标,但在 C# 5.0 中使用 VS2012+ 进行开发,您仍然可以使用 async/await,微软为此提供了 Microsoft.Bcl.Async 库。那么您的代码可能如下所示:

public class YourController : AsyncController
{
    async Task<string> YourMethodAsyncImpl(string url)
    {
        var request = (HttpWebRequest)WebRequest.Create(url);
        using (var response = await request.GetResponseAsync()
        using (var streamReader = new StreamReader(response.GetResponseStream())
            return await streamReader.ReadToEndAsync();
    }

    public void YourMethodAsyc(string url)
    {
        AsyncManager.OutstandingOperations.Increment();

        YourMethodAsyncImpl(url).ContinueWith(resultTask =>
        {
            try
            {
                AsyncManager.Parameters["data"] = resultTask.Result;
            }
            finally
            {
                AsyncManager.OutstandingOperations.Decrement();
            }
        }, TaskScheduler.FromCurrentSynchronizationContext());
    }

    public ActionResult YourMethodCompleted(string data)
    {
        return View("Data", new ViewModel
        {
            Data = data
        });
    }
}

【讨论】:

    【解决方案2】:

    我会为此使用 Microsoft 的反应式框架。 (NuGet“Rx-Main”)

    然后就超级简单了。

    这样定义你的函数:

    public IObservable<string> CallServiceNonBlocking(string url)
    {
        return Observable.Start(() =>
        {
            var httpRequest = (HttpWebRequest)WebRequest.Create(url);
            HttpResponse res = httpRequest.FetchBlocking();
            return res.GetValue(); 
        });
    }
    

    然后这样称呼它:

    CallServiceNonBlocking(url)
        .Subscribe(response =>
        {
            /* handle response here */
        });
    

    很容易使用ObserveOn 使订阅在 UI 线程或当前上下文等上运行。

    【讨论】:

    • 我认为这不会帮助 OP 的 Web 服务更好地扩展,因为它仍然阻塞了 FetchBlocking 的至少一个池线程。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-15
    • 1970-01-01
    • 1970-01-01
    • 2011-09-02
    • 1970-01-01
    相关资源
    最近更新 更多