Starting async method as Thread or as Task

2020-07-09 03:29发布

问题:

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

回答1:

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.



回答2:

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



回答3:

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().



回答4:

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


回答5:

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.