it-roy-ru.com

Запуск асинхронного метода как Thread или как Task

Я новичок в C#s await/async и в настоящее время играю немного.

В моем сценарии у меня есть простой клиент-объект, который имеет свойство WebRequest. Клиент должен периодически отправлять живые сообщения через WebRequests RequestStream. Это конструктор объекта client:

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

    IsRunning = true;

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

и асинхронный метод живого отправителя

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

Как запустить метод?

новый поток (SendAliveMessageAsync) .Start ();

или же

Task.Run (SendAliveMessageAsync); // меняем тип возвращаемого значения на Task

или же

await SendAliveMessageAsync (); // терпит неудачу, так как конструктор не является асинхронным

Мой вопрос больше касается моего личного понимания await/async, которое, я думаю, может быть неверным в некоторых моментах.

Третий вариант - метание

The 'await' operator can only be used in a method or lambda marked with the 'async' modifier
6
KingKerosin

Как запустить метод?

Я голосую за «ничего из вышеперечисленного». :)

«Огонь и забудь» - сложный сценарий, с которым нужно правильно справиться. В частности, обработка ошибок всегда проблематична. В этом случае async void может вас удивить.

Я предпочитаю явно сохранять задачи, если я не awaiting их сразу:

private async Task SendAliveMessageAsync();

public Task KeepaliveTask { get; private set; }

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

Это, по крайней мере, позволяет потребителям Client обнаруживать и восстанавливать исключения, вызванные методом SendAliveMessageAsync.

Кстати, этот шаблон почти эквивалентен моему шаблону "асинхронной инициализации" .

7
Stephen Cleary

Отредактировано, так как предыдущий ответ был неверным:

Поскольку это в конструкторе, я думаю, что вам придется раскрутить новый поток для него. Я лично сделал бы это, используя

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

3
John Clifford

Так как это огонь и забыл операция, вы должны начать его с помощью 

SendAliveMessageAsync();

Обратите внимание, что await не запускает новую Task. Это просто синтаксический сахар, ожидающий завершения Task.
Новый поток запускается с использованием Task.Run.

Итак, внутри SendAliveMessageAsync вы должны начать новую задачу:

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

Вот вариант, так как вы не можете вызвать await внутри конструктора.

Я бы предложил использовать Microsoft Reactive Framework (NuGet "Rx-Main").

Код будет выглядеть так:

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

Этот код обрабатывает все требуемые потоки и корректно удаляет StreamWriter по мере необходимости.

Всякий раз, когда вы хотите остановить сохранение активности, просто позвоните _subscription.Dispose().

0
Enigmativity

В вашем коде нет необходимости использовать async/await, просто установите новый поток для выполнения длительных операций.

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

Использование Task.Factory.StartNew(SendAliveMessage, TaskCreationOptions.LongRunning) для выполнения операции.

Если вы действительно хотите использовать шаблон async/await, просто вызовите его в конструкторе без модификатора await и забудьте об этом.

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

    IsRunning = true;

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

Я думаю, что это не очень хорошая идея - создать долго работающий поток или использовать шаблон async/await. Таймеры могут быть более подходящими в этой ситуации.

0
cFish