5

I have lots of code like this:

        var feed = new DataFeed(host, port);
        feed.OnConnected += (conn) =>
        {
            feed.BeginLogin(user, pass);
        };
        feed.OnReady += (f) =>
        {
             //Now I'm ready to do stuff.
        };

        feed.BeginConnect();

As you can see, I use the usual way of doing async operations. how do I change this code to use async await? Preferably something like this:

public async void InitConnection()
{
    await feed.BeginConnect();
    await feed.BeginLogin(user, pass);

    //Now I'm ready
}
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
nakiya
  • 14,063
  • 21
  • 79
  • 118
  • Does BeginXXX operations return IAsyncResult? – Sergey Berezovskiy Jun 12 '14 at 07:08
  • @SergeyBerezovskiy: For example `BeginConnect` returns void. Internally it calls `Socket.BeginConnect` and waits for `Socket.EndConnect` to be called. Once that is called, it fires off the `OnConnected` event. – nakiya Jun 12 '14 at 07:11
  • 2
    That kind of defeats the purpose of it being asynchronous, doesn't it? How is that different from just calling `Connect` outright? – Luaan Jun 12 '14 at 07:13
  • @Luaan : Supposing it did return `IAsyncResult`? – nakiya Jun 12 '14 at 07:17
  • If it did, you could use `Task.FromAsync`. – Luaan Jun 12 '14 at 07:24
  • @nakiya, check ["A reusable pattern to convert event into task"](http://stackoverflow.com/questions/22783741/a-reusable-pattern-to-convert-event-into-task). – noseratio Jun 12 '14 at 08:09

2 Answers2

10

You can use TaskCompletionSource<T> to wrap your EAP (event-based async pattern) into Tasks. It's not clear how you handle errors and cancel operations in your DataFeed class, so you will need to modify this code and add error handling (sample):

private Task ConnectAsync(DataFeed feed)
{
    var tcs = new TaskCompletionSource<object>();
    feed.OnConnected += _ => tcs.TrySetResult(null);
    feed.BeginConnect();
    return tcs.Task;
}

private Task LoginAsync(DataFeed feed, string user, string password)
{
    var tcs = new TaskCompletionSource<object>();
    feed.OnReady += _ => tcs.TrySetResult(null);
    feed.BeginLogin(user, pass);
    return tcs.Task;
}

Now you can use these methods:

public async void InitConnection()
{
    var feed = new DataFeed(host, port);
    await ConnectAsync(feed);
    await LoadAsync(feed, user, pass);
    //Now I'm ready
}

Note - you can move these async methods to DataFeed class. But if you can modify DataFeed, then better use TaskFactory.FromAsync to wrap APM API to Tasks.

Unfortunately there is no non-generic TaskCompletionSource which would return non-generic Task so, usually workaround is usage of Task<object>.

Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • I don't see `async` keyword in you methods. Shouldn't there be one? – nakiya Jun 12 '14 at 07:52
  • 2
    +1, this is what `TaskCompletetionSource` is for. One problem, what happens on failure? Out of interest, is there any benefit using a `TaskCompletetionSource` versus a `TaskCompletetionSource`, I suspect it makes no difference? – Jodrell Jun 12 '14 at 07:52
  • @nakiya no, they don't `await` anything. Any function that returns a `Task` is "awaitable". – Jodrell Jun 12 '14 at 07:53
  • @nakiya no, there should not be `async` on these methods - nothing is awaited inside. These methods just return Tasks – Sergey Berezovskiy Jun 12 '14 at 07:54
  • 2
    You could still have the methods return `Task` since `Task` inherits from `Task`. – Mike Zboray Jun 12 '14 at 07:55
  • @Jodrell yes, there is no difference. You can use bool :) But I think boolean is a kind of value, and simple object does not carry any information, so I like to use it instead. On failure OP should handle it and set exception to TaskCompletionSource – Sergey Berezovskiy Jun 12 '14 at 07:56
  • @SergeyBerezovskiy : `OnReady` gets called when I receive the login response and identify it as correct. So there's no direct callback for that like `EndConnect`. I fire off `OnReady` when that happens. How to convert to async await if I remove that event? – nakiya Jun 12 '14 at 07:57
  • @nakiya if there is no EndLogin, then you can simply use EAP wrapper as above. Sorry, need to go now – Sergey Berezovskiy Jun 12 '14 at 08:03
0

You need to change your DataFeed class to support that. You'll just have to use the task asynchronous pattern in there. That means that all the asynchronous methods in DataFeed have to return a Task (or some Task<T>), and they should be named ConnectAsync (for example).

Now, with Socket, this isn't entirely easy, because the XXXAsync methods on Socket aren't actually awaitable! The easiest way is to simply use TcpClient and TcpListener respectivelly (provided you're using TCP):

public async Task<bool> LoginAsync(TcpClient client, ...)
{
  var stream = client.GetStream();

  await stream.WriteAsync(...);

  // Make sure you read all that you need, and ideally no more. This is where
  // TCP can get very tricky...
  var bytesRead = await stream.ReadAsync(buf, ...);

  return CheckTheLoginResponse(buf);
}

and then just use it from the outside:

await client.ConnectAsync(...);

if (!(await LoginAsync(client, ...))) throw new UnauthorizedException(...);

// We're logged in

This is just sample code, I assume you're actually able to write decent TCP code to start with. If you do, writing it asynchronously using await isn't really much harder. Just make sure you're always awaiting some asynchronous I/O operation.

If you want to do the same using just Socket, you will probably have to use Task.FromAsync, which provides a wrapper around the BeginXXX / EndXXX methods - very handy.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • This isn't ideal as I will be receiving heart beats and whatnot from the server even before I receive login response. – nakiya Jun 12 '14 at 07:31
  • @nakiya Well, yeah, that's why you need to avoid using Write and Read directly. There should be a message processor that handles the messages, and you should be awaiting that. So instead of `await stream.ReadAsync`, you'd `await messageProcessor.WaitForMessageAsync(...)` or something. It's all about how deep you want to go in supporting the awaitability - if you want to go slow, wrapping events in `TaskCompletionSource` as suggested by Sergey is a great way. If you don't mind changing everything to the awaitable pattern, my way might be better. It does need more pipework, though. – Luaan Jun 12 '14 at 10:41
  • How can I go about implementing `WaitForMessageAsync` you mentioned? I am confused. Can you perhaps provide with an example? – nakiya Jun 12 '14 at 13:43
  • @nakiya Yeah, that's the tricky part. Basically, you need to have an abstraction of the whole messaging thing on top of the TCP connection. Your application shouldn't care that it uses TCP at all, and this abstract message handler would process messages on its own and provide awaitable methods to both send and receive messages. I'm going to be showing how to do this in my TCP networking articles, but it might be a few weeks before I get to that. It's a bit more complicated than using events, although it makes it much easier to work with asynchronously from the outside. – Luaan Jun 13 '14 at 11:41