0

Situation

I have a raspberry pi set up as a server taking json via HTTP as input. The "API" allows to set leds connected to the pi. That all works, I can send requests from the browser and everything is great.

It takes a while for the response to arrive. That's why I want to communicate asynchrounously.

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;
}

For the top level overview, here's how my Main method looks like (it doesn't compile):

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

I hope that makes sense.

The Led class is just a data object holding 3 bytes for the 3 channels and some conversion methods to and from json.

The setLedAsync method:

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);
}

Error

This line is where I get an error for using await:

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

await can only be used in an async method.

Questions

  1. Why do I get this error? The last comment line in the example code

    // Any methods that are awaiting AccessTheWebAsync retrieve the length value.

    makes me think that this is how it should be done. As I understand it, the await basically unwrapps the Task<T> into a T.

    If I do not use await, I get a type missmatch, because the method returns Task<Led>, not Led.

  2. What's confusing for me is to understand the difference between the example and my situation. I have to use await twice in my async method:

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

    The thing is that I have to deal with this HttpResponseMessage as a middleman. I suspect that I'm prematurely giving up the asynchronousity with this second await somehow (if that makes any sense) I think this is the origin of the problem, but I'm not sure how to solve it.

Edit

I wrapped the function call in an asyn method, which allows to compile the code. But it's not asynchronous. I added a delay on the server side to test this.

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");
    }
}

None of the "after" messages are written to the console before the response from the request arrives.

null
  • 5,207
  • 1
  • 19
  • 35
  • 3
    The error is pretty clear: *"`await` can only be used in an `async` method"*. It's not about the method you are awaiting, it's about the method you are invoking it ***from***. – sstan Sep 24 '15 at 13:39
  • @sstan which is why I tried to remove it, which didn't work. I also explain how the official example code suggests that `await` should be used or at least that I think it does. Feel free to elaborate in an answer. – null Sep 24 '15 at 13:41
  • @sstan then why is the async method supposed to return a `Task`? – null Sep 24 '15 at 13:47
  • 1
    Is this a console app? You cannot asynchronously wait in the Main of a console app because *the program would end when the asynchronous wait returns*. There is then no longer a program to resume when the asynchronous data becomes available. – Eric Lippert Sep 24 '15 at 13:50
  • possible duplicate of https://stackoverflow.com/questions/9208921/async-on-main-method-of-console-app – Random Dev Sep 24 '15 at 13:54
  • @null `await` will not sit there and wait for the result - It's more akin to `task.ContinueWith(...the rest of your method)` – Random Dev Sep 24 '15 at 13:57
  • 1
    I think your fundamental problem here is that you are trying to learn how asynchronous programming works by looking at examples on the internet and then writing code without understanding what it does. That kind of exploratory learning is usually a good technique but asynchronous programming can be *really* complicated and hard to wrap your head around if you do not understand *precisely* what the semantics of the various operations are with respect to control flow. I would start by reading carefully some beginner articles on how await works and what it is for. – Eric Lippert Sep 24 '15 at 14:06
  • @EricLippert my fundamental problem is C#. If I did this in JS, I'd simply register a callback and be done with it. With frameworks I can get syntactic sugar if I want. In C# I get a response "_yeah, it's really complicated..._". Great! – null Sep 24 '15 at 14:23
  • 2
    Nothing is stopping you from registering a callback if you don't want to use await; that's what await does, is registers a callback for you. People told us that it was too hard to write programs that registered callbacks. Consider how for example you would register a callback that called back *into the middle of a nested loop that contained exception handling*. That is so hard to do in JS or C# that no one does it, but it is easy to do with await. – Eric Lippert Sep 24 '15 at 14:24
  • well, the complexity of doing it stops me – null Sep 24 '15 at 15:06

1 Answers1

12

You get the error because an asynchronous wait -- an await -- implies that the method doing the awaiting is itself asynchronous. I think you do not understand what await means. Await does not mean synchronously block until the result is available -- that is the opposite of what it means. Await means return immediately so my caller can do important work without waiting for this result; schedule the remainder of this method at some time in the future when the result is available. Await is an asynchronous wait. When you await a letter to arrive in the mail you do not sit by the door doing nothing until it arrives; you do other work asynchronously and then resume the task of reading your mail at some time after the letter arrives.

You say that the method -- the method doing the awaiting, not the method returning the task being awaited -- is Main. If this is a console app then you cannot make Main asynchronous because when Main returns the program ends. Again, internalize this: an await is just a return from the perspective of the current method. The current method will be called again later in the future and will pick up where it left off, but when Main returns there is no future. So you cannot make Main async.

You note that async turns a task of T into a T, which is correct, but it does so asynchronously. So your principal choices here are here are:

  • turn the task of T into a T synchronously from Main
  • write some sort of app other than a Console app; say, a winforms or WPF app, which does not terminate until you tell it to
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • I modified my application to not terminate after the call, but it does not look like it's asynchronous. – null Sep 24 '15 at 14:45