【问题标题】:Why can I not await the return value from my async method?为什么我不能等待异步方法的返回值?
【发布时间】:2015-09-24 22:44:32
【问题描述】:

情况

我将树莓派设置为服务器,通过 HTTP 将 json 作为输入。 “API”允许设置连接到 pi 的 LED。一切正常,我可以从浏览器发送请求,一切都很好。

响应到达需要一段时间。这就是我想要异步通信的原因。

I found this on msdn that explains how it's done.

// Three things to note in the signature:
//  - The method has an async modifier. 
//  - The return type is Task or Task<T>. (See "Return Types" section.)
//    Here, it is Task<int> because the return statement returns an integer.
//  - The method name ends in "Async."
async Task<int> AccessTheWebAsync()
{ 
    // You need to add a reference to System.Net.Http to declare client.
    HttpClient client = new HttpClient();

    // GetStringAsync returns a Task<string>. That means that when you await the
    // task you'll get a string (urlContents).
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

    // You can do work here that doesn't rely on the string from GetStringAsync.
    DoIndependentWork();

    // The await operator suspends AccessTheWebAsync.
    //  - AccessTheWebAsync can't continue until getStringTask is complete.
    //  - Meanwhile, control returns to the caller of AccessTheWebAsync.
    //  - Control resumes here when getStringTask is complete. 
    //  - The await operator then retrieves the string result from getStringTask.
    string urlContents = await getStringTask;

    // The return statement specifies an integer result.
    // Any methods that are awaiting AccessTheWebAsync retrieve the length value.
    return urlContents.Length;
}

对于顶级概述,这是我的 Main 方法的外观(它不编译):

var pi1 = new RaspberryPi(@"http://192.168.0.100:8080");    // specify IP
var led = new Led(255, 100, 180);                           // r, g, b values wrapped in an Led object

Led result = await pi1.setLedAsync(2, led);      // FAIL    // what should be an async POST, awaiting the response

我希望这是有道理的。

Led 类只是一个数据对象,为 3 个通道保存 3 个 bytes 以​​及一些与 json 之间的转换方法。

setLedAsync 方法:

async public Task<Led> setLedAsync(uint n, Led led)
{
    var client = new HttpClient();
    client.BaseAddress = _uri;

    var content = new StringContent(led.ToJson(), Encoding.UTF8, "application/json");

    Task<HttpResponseMessage> response = client.PutAsync("/led/" + n, content);
    HttpResponseMessage responseMessage = await response;

    string json = await responseMessage.Content.ReadAsStringAsync();

    return new Led(json);
}

错误

这一行是我在使用 await 时遇到错误的地方:

Led result = await pi1.setLedAsync(2, led);

await 只能在async 方法中使用。

问题

  1. 为什么会出现此错误?示例代码中的最后一行注释

    // 等待 AccessTheWebAsync 的任何方法都会检索长度值。

    让我觉得应该这样做。据我了解,await 基本上将Task&lt;T&gt; 解包为T

    如果我不使用await,我会得到类型不匹配,因为该方法返回Task&lt;Led&gt;,而不是Led

  2. 让我感到困惑的是理解示例和我的情况之间的区别。我必须在我的async 方法中使用两次await

    • HttpResponseMessage responseMessage = await response;
    • string json = await responseMessage.Content.ReadAsStringAsync();

    问题是我必须以中间人的身份处理这个HttpResponseMessage。我怀疑我过早地放弃了第二次等待的异步性(如果这有任何意义的话)我认为这是问题的根源,但我不知道如何解决它。

编辑

我将函数调用包装在一个允许编译代码的异步方法中。 但它不是异步的。我在服务器端添加了一个延迟来测试这个。

class Program
{
    static void Main(string[] args)
    {
        var prog = new Program();
        Console.WriteLine("before init");
        prog.init();
        Console.WriteLine("after init");   // not happening before the response arrives
        Console.Read();
    }

    private async void init()
    {
        var pi1 = new RaspberryPi(@"http://192.168.0.100:8080");    // specify IP
        var led = new Led(255, 0, 0);                               // r, g, b values wrapped in an Led object
        Console.WriteLine("before await");
        Led result = await pi1.setLedAsync(2, led);                 // what should be an async POST, awaiting the response
        Console.WriteLine("after await");
    }
}

在请求的响应到达之前,不会将任何“之后”消息写入控制台。

【问题讨论】:

  • 错误很明显:"await 只能在async 方法中使用"。这与您正在等待的方法无关,而与您正在调用它的方法有关来自
  • @sstan 这就是我尝试删除它的原因,但没有成功。我还解释了官方示例代码如何建议应该使用await,或者至少我认为是这样。随意在答案中详细说明。
  • 这是一个控制台应用程序吗?您不能在控制台应用程序的 Main 中异步等待,因为程序将在异步等待返回时结束。当异步数据可用时,不再有程序可以恢复。
  • 我认为您的根本问题是您试图通过查看 Internet 上的示例来了解异步编程的工作原理,然后在不了解其作用的情况下编写代码。这种探索性学习通常是一种很好的技术,但如果您不准确地了解各种语义的含义,异步编程可能真的复杂且难以理解操作是关于控制流的。我会先仔细阅读一些关于 await 工作原理和用途的初学者文章。
  • 如果您不想使用 await,没有什么能阻止您注册回调;这就是 await 所做的,就是为你注册一个回调。人们告诉我们,编写注册回调的程序太难了。例如,考虑如何注册一个回调,该回调将回调到包含异常处理的嵌套循环的中间。这在 JS 或 C# 中很难做到,没有人能做到,但用 await 很容易做到。

标签: c# .net asynchronous async-await


【解决方案1】:

您收到错误是因为异步等待——等待——意味着执行等待的方法本身是异步的。我想你不明白等待是什么意思。 Await 不是的意思是同步阻塞,直到结果可用——这与它的意思相反。等待意味着立即返回,这样我的调用者就可以做重要的工作而无需等待这个结果;当结果可用时,在将来某个时间安排此方法的其余部分。 Await 是一个异步等待。当你等待一封信送达时,你不会坐在门前什么都不做,直到它到达;您异步执行其他工作,然后在信件到达后的某个时间恢复阅读邮件的任务。

你说方法——执行等待的方法,而不是返回正在等待的任务的方法——是 Main。如果这是一个控制台应用程序,那么您不能使 Main 异步,因为 当 Main 返回程序时结束。再次内化:从当前方法的角度来看,await 只是一个返回。当前方法将在以后再次被调用,并将从中断的地方继续,但是当 Main 返回时,没有未来。所以你不能让 Main 异步。

您注意到 async 将 T 的任务转换为 T,这是正确的,但它这样做是 异步的。所以你在这里的主要选择是:

  • 将T的任务从Main同步转为T
  • 编写除控制台应用程序之外的某种应用程序;比如说,一个 winforms 或 WPF 应用程序,在你告诉它之前它不会终止

【讨论】:

  • 我将我的应用程序修改为在调用后不终止,但它看起来不像是异步的。
猜你喜欢
  • 1970-01-01
  • 2017-08-30
  • 2020-03-13
  • 1970-01-01
  • 2017-03-21
  • 2017-05-30
  • 2015-09-21
  • 1970-01-01
  • 2013-02-14
相关资源
最近更新 更多