【问题标题】:Converting ordinary Http Post web request with Async and Await使用 Async 和 Await 转换普通的 Http Post Web 请求
【发布时间】:2014-01-15 15:50:45
【问题描述】:

如何使用 Async / Await 模式转换我的传统 HttpWebRequest “POST”调用,在这里我附上我当前的代码,请任何人帮助我使用 Windows phone 8 的 Async / Await 模式转换此代码。

public void GetEnvironmentVariables(Action<Credentials> getResultCallback, Action<Exception> getErrorCallback)
{
    CredentialsCallback = getResultCallback;
    ErrorCallback = getErrorCallback;
    var uri = new Uri(BaseUri);
    var request = (HttpWebRequest)WebRequest.Create(uri);
    request.Method = "POST";
    request.ContentType = "application/json";
    var jsonObject = new JObject
    {
        new JProperty("apiKey",_api),
        new JProperty("affiliateId",_affid),
    };
    var serializedResult = JsonConvert.SerializeObject(jsonObject);
    byte[] requestBody = Encoding.UTF8.GetBytes(serializedResult);

    request.BeginGetRequestStream(GetRequestStreamCallback, new object[] { request, requestBody });

}

private void GetRequestStreamCallback(IAsyncResult asynchronousResult)
{
    var request = (HttpWebRequest)((object[])asynchronousResult.AsyncState)[0];
    using (var postStream = request.EndGetRequestStream(asynchronousResult))
    {
        var byteArray = (byte[])((object[])asynchronousResult.AsyncState)[1];

        // Write to the request stream.
        postStream.Write(byteArray, 0, byteArray.Length);

    }
    request.BeginGetResponse(GetResponseCallback, request);
}

private void GetResponseCallback(IAsyncResult asynchronousResult)
{
    var request = (HttpWebRequest)asynchronousResult.AsyncState;
    try
    {
        var response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
        if (response != null)
        {
            var reader = new StreamReader(response.GetResponseStream());
            string responseString = reader.ReadToEnd();
            Credentails = JsonConvert.DeserializeObject<Credentials>(responseString);
            if (Credentails != null && string.IsNullOrEmpty(Credentails.Err))
                CredentialsCallback(Credentails);
            else
            {
                if (Credentails != null)
                    ErrorCallback(new Exception(string.Format("Error Code : {0}", StorageCredentails.Err)));
            }
        }
    }
    catch (WebException we)
    {
            var reader = new StreamReader(we.Response.GetResponseStream());
            string responseString = reader.ReadToEnd();
            Debug.WriteLine(responseString);
            ErrorCallback(we);

    }
} 

【问题讨论】:

  • What have you tried?您的代码遇到了什么困难?
  • 我是这个世界的新手,通过设置响应对象,我厌倦了使用“TaskCompletionSource”的实现。但这似乎是我想念的根源。我正在寻找一个有经验的人的完美实施,如果我得到这样的支持,这对像我这样的学生会有帮助。
  • 不要犹豫,展示您目前拥有的代码。通过这种方式,人们将能够看到并解释它的问题所在,并希望为您提供更好的选择。但恐怕现在您正在要求其他人为您完成这项工作,而 StackOverflow 不适合此类请求。
  • 不,我不是这个意思。正如我所说,我对这个环境很陌生,对语言特性知之甚少。如果我将代码放在这里,阅读代码的人可能会误解,因为我不知道如何解释我的真正问题。如果您有耐心帮助我,请给我指导方针,否则请忽略我的问题。

标签: c# windows-phone-8 async-await c#-5.0


【解决方案1】:

由于 Windows Phone 8 似乎没有提供您需要的 TAP 方法,例如 GetRequestStreamAsync,所以要做的第一件事就是编写一个小包装器来为自己提供它们:

public static class WebRequestAsyncExtensions
{
    public static Task<Stream> GetRequestStreamAsync(this WebRequest request)
    {
        return Task.Factory.FromAsync<Stream>(
            request.BeginGetRequestStream, request.EndGetRequestStream, null);
    }

    public static Task<WebResponse> GetResponseAsync(this WebRequest request)
    {
        return Task.Factory.FromAsync<WebResponse>(
            request.BeginGetResponse, request.EndGetResponse, null);
    }
}

注意Task.Factory.FromAsync 的使用——这是在基于APM 的异步API(例如WebRequest 提供的API)周围获得await 友好包装器的首选方式。这比其他人建议的使用Task.Factory.StartNew 效率更高,因为这会启动一个新线程,而这不需要。

有了这个,您现在可以像在这些 TAP 样式方法可用的平台(例如 Windows 8 商店应用程序、桌面应用程序等)上一样编写代码:

public async Task GetEnvironmentVariablesAsync(Action<Credentials> getResultCallback, Action<Exception> getErrorCallback)
{
    CredentialsCallback = getResultCallback;
    ErrorCallback = getErrorCallback;
    var uri = new Uri(BaseUri);
    var request = (HttpWebRequest) WebRequest.Create(uri);
    request.Method = "POST";
    request.ContentType = "application/json";
    var jsonObject = new JObject
    {
        new JProperty("apiKey",_api),
        new JProperty("affiliateId",_affid),
    };
    var serializedResult = JsonConvert.SerializeObject(jsonObject);
    byte[] requestBody = Encoding.UTF8.GetBytes(serializedResult);

    // ASYNC: using awaitable wrapper to get request stream
    using (var postStream = await request.GetRequestStreamAsync())
    {
        // Write to the request stream.
        // ASYNC: writing to the POST stream can be slow
        await postStream.WriteAsync(requestBody, 0, requestBody.Length);
    }

    try
    {
        // ASYNC: using awaitable wrapper to get response
        var response = (HttpWebResponse) await request.GetResponseAsync();
        if (response != null)
        {
            var reader = new StreamReader(response.GetResponseStream());
            // ASYNC: using StreamReader's async method to read to end, in case
            // the stream i slarge.
            string responseString = await reader.ReadToEndAsync();
            Credentails = JsonConvert.DeserializeObject<Credentials>(responseString);
            if (Credentails != null && string.IsNullOrEmpty(Credentails.Err))
                CredentialsCallback(Credentails);
            else
            {
                if (Credentails != null)
                    ErrorCallback(new Exception(string.Format("Error Code : {0}", StorageCredentails.Err)));
            }
        }
    }
    catch (WebException we)
    {
        var reader = new StreamReader(we.Response.GetResponseStream());
        string responseString = reader.ReadToEnd();
        Debug.WriteLine(responseString);
        ErrorCallback(we);

    }
}

注意带有// ASYNC: cmets 的四行 - 这些显示了我所做的更改。我已将您的方法缩减为一种,因为这是 a) 一旦您使用 asyncawait 就可以实现,并且 b) 比尝试使用状态参数将事物从一种方法传递到另一种方法要容易得多。

请注意,其中的第二个和第四个实际上使您之前同步执行的一些事情变得异步:将数据写入请求流,并从响应流中读取数据。对于一个小请求,这可能无关紧要,但如果正在传输大量数据,对WriteReadToEnd 的同步调用可能会阻塞。幸运的是,尽管 Windows Phone 8 似乎缺少 WebRequest 上的 TAP 方法,但它确实在 StreamStreamReader 上提供了它们,因此无需编写任何扩展方法即可工作。

【讨论】:

  • 顺便说一句,您要求从“可靠和/或官方来源”获得答案。为了回答这个问题,我编写了 O'Reilly 的“Programming C#”的最新版本——完全重写。我还编写了 Pluralsight 关于异步使用任务并行库的课程,以及关于 C# 5 中新的异步语言特性的课程。
  • @IanGriffiths,我正在将异步添加到现有的 WebClient 包装器库中,这很有帮助:谢谢。我习惯于用using 包裹我所有的Streams 和Readers,但我看到你在这里不这样做。这是因为 Windows 8 Phone 缺少这一点,异步是不好的做法,还是不值得?
  • @Brad 我的代码或多或少直接基于问题中的代码。它对请求流使用using 语句,但不打扰响应流。我的代码完全一样。我的目标是尽可能轻松地查看我的代码与原始代码的关系;修复缺少using 语句会使连接更难看到。所以这就是我把它们排除在外的原因——做正确的事会掩盖我试图说明的具体点。您应该将它们放入生产代码中。
  • @IanGriffiths,在回答问题时要牢记这一目标。感谢您的澄清。我遇到了问题,但最终不尊重异步上下文并导致死锁。
【解决方案2】:

我是社区的新手,所以这是我的第一篇文章。在这种情况下,您可以使用通用任务返回任何类型。过去这对我来说效果很好。

服务器端

public class MyController : ApiController
{
    public Task<string> PostAsync()
    {
        return Task.Factory.StartNew(() =>
        {
            return "populate me with any type and data, but change the type in the response signature.";
        });
    }
}

客户端

public class HomeController : Controller
{
    public Task<ViewResult> Index()
    {
        return Task.Factory.StartNew(() =>
        {
            var model = "use a provider, get some data, or something";
            return View(model);
        });
    }
}

【讨论】:

  • 虽然当您需要在异步包装器中包装基本同步的东西时,这种方法是合适的,但本示例并非如此。这个问题涉及到一个 HttpWebRequest,它确实提供了异步服务,只是不是 C# 5 所期望的形式。它使用自 .NET 1.0 以来一直存在的老式异步编程模型 (APM)。您可以使用 Task.Factory.FromAsync 方法将 APM 操作高效地包装为无线程任务,这是一种更高效的解决方案。
【解决方案3】:

这应该可以完成工作:

    public async void GetEnvironmentVariables(Action<Credentials> getResultCallback, Action<Exception> getErrorCallback) {
        CredentialsCallback = getResultCallback;
        ErrorCallback = getErrorCallback;
        var uri = new Uri(BaseUri);
        var request = (HttpWebRequest)WebRequest.Create(uri);
        request.Method = "POST";
        request.ContentType = "application/json";

        var jsonObject = new JObject {
            new JProperty("apiKey", _api),
            new JProperty("affiliateId", _affid),
        };

        var serializedResult = JsonConvert.SerializeObject(jsonObject);
        var requestBody = Encoding.UTF8.GetBytes(serializedResult);

        var requestStream = request.GetRequestStream();
        requestStream.Write(requestBody, 0, requestBody.Length);

        await GetResponse(request);
    }

    private async Task GetResponse(WebRequest request) {
        Stream resStream = null;

        try {
            var response = await request.GetResponseAsync();

            if (response == null) {
                return;
            }

            resStream = response.GetResponseStream();
            if (resStream == null) {
                return;
            }

            var reader = new StreamReader(resStream);
            var responseString = await reader.ReadToEndAsync();
            Credentails = JsonConvert.DeserializeObject<Credentials>(responseString);
            if (Credentails != null && string.IsNullOrEmpty(Credentails.Err)) {
                CredentialsCallback(Credentails);
            }
            else {
                if (Credentails != null) {
                    ErrorCallback(new Exception(string.Format("Error Code : {0}", StorageCredentails.Err)));
                }
            }
        }
        catch (WebException we) {
            if (resStream != null) {
                var reader = new StreamReader(resStream);
                var responseString = reader.ReadToEnd();
                Debug.WriteLine(responseString);
            }
            ErrorCallback(we);
        }
    }

【讨论】:

  • 问题是关于 Windows Phone 8,它不提供您在此处使用的 GetResponseAsync 方法。 (不过,您可以编写自己的版本,正如我的回答所示。)此外,您只将三个潜在显示位中的两个设为异步:对 GetResponseStream 的调用也可能会阻塞。
  • ...事实上我已经意识到有四个地方可以异步,写入请求流也可能会阻塞。 (我已经编辑了我的回复以反映这一点。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-03
  • 1970-01-01
  • 2018-01-22
  • 2019-05-08
  • 1970-01-01
  • 2020-03-14
  • 2020-07-27
相关资源
最近更新 更多