20

I'm new to C#s await/async and currently playing around a bit.

In my scenario I have a simple client-object which has a WebRequest property. The client should send periodically alive-messages over the WebRequests RequestStream. This is the constructor of the client-object:

public Client()
{
    _webRequest = WebRequest.Create("some url");
    _webRequest.Method = "POST";

    IsRunning = true;

    // --> how to start the 'async' method (see below)
}

and the async alive-sender method

private async void SendAliveMessageAsync()
{
    const string keepAliveMessage = "{\"message\": {\"type\": \"keepalive\"}}";
    var seconds = 0;
    while (IsRunning)
    {
        if (seconds % 10 == 0)
        {
            await new StreamWriter(_webRequest.GetRequestStream()).WriteLineAsync(keepAliveMessage);
        }

        await Task.Delay(1000);
        seconds++;
    }
}

How should the method be started?

new Thread(SendAliveMessageAsync).Start();

or

Task.Run(SendAliveMessageAsync); // changing the returning type to Task

or

await SendAliveMessageAsync(); // fails as of the constructor is not async

My question is more about my personal understanding of await/async which I guess may be wrong in some points.

The third option is throwing

The 'await' operator can only be used in a method or lambda marked with the 'async' modifier
KingKerosin
  • 3,639
  • 4
  • 38
  • 77
  • First you have to decide whether this is a fire-and-forget method or something you want to wait for. – Lasse V. Karlsen Jan 15 '16 at 09:38
  • It is indeed a fire-and-forget as it simply should start a `Thread` that send alive messages. – KingKerosin Jan 15 '16 at 09:40
  • 1
    Then `await` is pointless as you're not really interested in waiting for it to complete. I would simply use the thread approach then. Personally I consider tasks as smaller contained units of work that needs to be run asynchronously. – Lasse V. Karlsen Jan 15 '16 at 09:42
  • 3
    You should never call `new StreamWriter(_webRequest.GetRequestStream()).WriteLineAsync(keepAliveMessage)` - await or otherwise - as `StreamWriter` is `IDisposable` and must be properly disposed of once used. This syntax doesn't allow you to call `.Dipose()`. – Enigmativity Jan 15 '16 at 09:43
  • @Enigmativity Good and valid point. But this is not the working code. The `StreamWriter` is also initialized in the constructor but for simplicity reasons not shown here. – KingKerosin Jan 15 '16 at 09:49

5 Answers5

13

How should the method be started?

I vote for "none of the above". :)

"Fire and forget" is a difficult scenario to handle correctly. In particular, error handling is always problematic. In this case, async void may surprise you.

I prefer to explicitly save the tasks if I'm not awaiting them immediately:

private async Task SendAliveMessageAsync();

public Task KeepaliveTask { get; private set; }

public Client()
{
  ...
  KeepaliveTask = SendAliveMessageAsync();
}

This at least allows the consumers of Client to detect and recover from exceptions thrown by the SendAliveMessageAsync method.

On a side note, this pattern is almost equivalent to my "asynchronous initialization" pattern.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
8

Edited as previous answer was wrong:

As it's in the constructor, I think you would have to spin up a new thread for it. I would personally do that using

Task.Factory.StartNew(() => SendAliveMessageAsync());

John Clifford
  • 821
  • 5
  • 10
  • The code is being run in the constructor. Calling `await` doesn't work in constructors. – Enigmativity Jan 15 '16 at 09:40
  • @JohnClifford Is there any difference between `Task.Run` and `Task.Factory.StartNew`? Or is this just your personal opinion? – KingKerosin Jan 15 '16 at 09:53
  • 1
    To be honest I use Task.Factory.StartNew out of force of habit; Task.Run is pretty much a shortcut for it, so if you'd rather use that go right ahead. – John Clifford Jan 15 '16 at 09:55
1

Here's an option since you can't call await from inside a constructor.

I would suggest using Microsoft's Reactive Framework (NuGet "Rx-Main").

The code would look like this:

public class Client
{
    System.Net.WebRequest _webRequest = null;
    IDisposable _subscription = null;

    public Client()
    {
        _webRequest = System.Net.WebRequest.Create("some url");
        _webRequest.Method = "POST";

        const string keepAliveMessage = "{\"message\": {\"type\": \"keepalive\"}}";

        var keepAlives =
            from n in Observable.Interval(TimeSpan.FromSeconds(10.0))
            from u in Observable.Using(
                () => new StreamWriter(_webRequest.GetRequestStream()),
                sw => Observable.FromAsync(() => sw.WriteLineAsync(keepAliveMessage)))
            select u;

        _subscription = keepAlives.Subscribe();
    }
}

This code handles all of the threading required and properly disposes of the StreamWriter as it goes.

Whenever you want to stop the keep alives just call _subscription.Dispose().

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
1

In your code there is no need to using async/await, just set up a new thread to perform long operation.

private void SendAliveMessage()
{
    const string keepAliveMessage = "{\"message\": {\"type\": \"keepalive\"}}";
    var sreamWriter = new StreamWriter(_webRequest.GetRequestStream());
    while (IsRunning)
    {
        sreamWriter.WriteLine(keepAliveMessage);
        Thread.Sleep(10 * 1000);
    }    
}

Using Task.Factory.StartNew(SendAliveMessage, TaskCreationOptions.LongRunning) to perform the operation.

If you really want to using async/await pattern, just call it in constructor without await modifier and the forget it.

public Client()
{
    _webRequest = WebRequest.Create("some url");
    _webRequest.Method = "POST";

    IsRunning = true;

    SendAliveMessageAsync();    //just call it and forget it.
}

I think it's not good idea to set up a long running thread or using async/await pattern. Timers maybe more suitable in this situation.

cFish
  • 53
  • 6
0

Since it is a fire and forget operation, you should start it using

SendAliveMessageAsync();

Note that await does not start a new Task. It is just syntactical sugar to wait for a Task to complete.
A new thread is started using Task.Run.

So inside SendAliveMessageAsync you should start a new Task:

private async Task SendAliveMessageAsync()
{
    const string keepAliveMessage = "{\"message\": {\"type\": \"keepalive\"}}";
    await Task.Run( () => {
        var seconds = 0;
        while (IsRunning)
        {
            if (seconds % 10 == 0)
            {
                await new StreamWriter(_webRequest.GetRequestStream()).WriteLineAsync(keepAliveMessage);
            }

            await Task.Delay(1000);
            seconds++;
        }
    });
}
Domysee
  • 12,718
  • 10
  • 53
  • 84
  • The code is being run in the constructor. Calling `await` doesn't work in constructors. – Enigmativity Jan 15 '16 at 09:40
  • But is calling it with `SendAliveMessageAsync();` not synchronously and therefore the Constructor will never return (because of the methods `while`)? – KingKerosin Jan 15 '16 at 09:42
  • @Enigmativity oh wow, how could I miss that. Thanks for the reminder – Domysee Jan 15 '16 at 09:42
  • @KingKerosin - Yes, that loop in a constructor is bad. Constructors should complete asap and should **never** risk a exception being thrown. – Enigmativity Jan 15 '16 at 09:44