Как вызвать asyn c метод в потоке, но ждать его в c# - PullRequest
0 голосов
/ 21 апреля 2020

У меня есть ветка, которая отвечает за вызов вебапи с 4 сайтов ровно каждые 2 секунды. Не следует ожидать метода вызова Webapi, поскольку, если веб-сайт недоступен, он будет ждать 5 секунд, чтобы получить тайм-аут, а затем следующий вызов веб-сайта будет отложен. Поскольку HttpClient в. NET 4.7.2 имеет только асинхронные c методы, его следует использовать с await, а если нет, компилятор выдает предупреждение, и мы можем получить непредвиденное поведение (как говорит Microsoft). Поэтому я должен использовать Task.Run или вызвать Threadpool.QueueUserWorkItem для параллельного вызова webapi. Вот судокод:

public class Test1
{
        private AutoResetEvent waitEvent = new AutoResetEvent(false);
        private volatile bool _terminated = false;

        public void Start()
        {
            Thread T = new Thread(ProcThread);
            T.Start();
        }

        private async void ProcThread()
        {
            while (!_terminated)
            {
                await CallWebApi();    <=========== this line 
                waitEvent.WaitOne(2000);
            }

        }

        private async Task CallWebApi()
        {
            HttpClient client = new HttpClient();
            .....
            .....
        }

 }

Ответы [ 3 ]

2 голосов
/ 22 апреля 2020

Итак, у вас есть асинхронная c процедура, которая использует HttpClient для извлечения некоторой информации и обработки извлеченных данных:

async Task CallWebApiAsync() {...}

Улучшение 1 : рекомендуется суффиксировать asyn c методы с asyn c. Это сделано для того, чтобы позволить асин c версии существовать рядом с неасин c версией, которая делает что-то похожее.

Внутри этого метода вы используете один из методов HttpClient для выборки информация. Поскольку CallWebApiAsyn c является ожидаемым, я предполагаю, что используются методы asyn c (GetAsyn c, GetStreamAsyn c, et c), и что метод ожидает только тогда, когда ему нужен результат asyn c method.

Приятно то, что, как пользователь CallWebApiAsyn c, до тех пор, пока вы не ожидаете звонка, вы можете делать другие вещи, даже если веб-сайт не реагирует Проблема в том, что через 2 секунды вы хотите снова вызвать метод. Но что делать, если метод еще не завершен.

Улучшение 2 Поскольку вы хотите иметь возможность начать новую задачу, в то время как предыдущая еще не завершена: запомните запущенную задач, и выбросить их, когда закончите.

HashSet<Task> activeTasks = new HashSet<Task>(); // efficient add, lookup, and removal

void TaskStarted(Task startedTask)
{
    // remember the startedTask
    activeTasks.Add(startedTask);
}

void TaskCompleted(Task completedTask)
{
    // If desired: log or process the results
    LogFinishedTask(completedTask);

    // Remove the completedTask from the set of ActiveTasks:
    activeTasks.Remove(completedTask);        
}

Может быть удобно удалить все выполненные задачи сразу:

void RemoveCompletedTasks()
{
    var completedTasks = activeTasks.Where(task => task.IsCompleted).ToList();
    foreach (var task in completedTasks)
    {
        TaskCompleted(completedTask);
    }
}

Теперь мы можем настроить ваш ProcThread.

Улучшение 3 : в asyn c -aaait всегда возвращает Task вместо void и Task<TResult> вместо TResult. Единственное исключение: обработчики событий возвращают void.

async Task ProcThread()
{
    // Repeatedly: start a task; remember it, and wait 2 seconds
    TimeSpan waitTime = TimeSpan.FromSeconds(2);

    while (!terminationRequested)
    {
        Task taskWebApi = CallWebApiAsync();

        // You didn't await, so you are free to do other things
        // Remember the task that you started.
        this.TaskStarted(taskWebApi);

        // wait a while before you start new task:
        await Task.Delay(waitTime);

        // before starting a new task, remove all completed tasks
        this.RemoveCompletedTasks();            
    }
}

Улучшение 4 : использование TimeSpan.

TimeSpan.FromSeconds(2) гораздо проще понять, что оно представляет, чем значение 2000.

Как остановить?

Проблема, конечно, после того, как вы запросите завершение, могут все еще выполняться некоторые задачи. Вам придется ждать их до конца sh. Но даже тогда: некоторые задачи могут вообще не завершиться sh в течение разумного времени.

Улучшение 5 : используйте CancellationToken для запроса отмены.

Чтобы отменить задачи в аккуратно, класс CancellationToken придуман. Пользователи, которые запускают задачу, создают объект CancellationTokenSource и запрашивают у него CancellationToken. Этот токен передается всем asyn c методам. Как только пользователь хочет отменить все задачи, которые были запущены с использованием этого CancellationTokenSource, он просит CancellationTokenSource отменить. Все задачи, имеющие токен из этого источника, обещали регулярно проверять токен, чтобы узнать, требуется ли отмена. Если это так, задача выполняет некоторую очистку (при необходимости) и возвращает.

Все сводится в один класс:

class Test1
{
    private HttpClient httpClient = new HttpClient(...);
    private HashSet<TTask> activeTasks = new HashSet<TTask>();

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        // repeated CallWebApiAsync until cancellation is requested
        TimeSpan waitTime = TimeSpan.FromSeconds(2);

        // repeat the following until OperationCancelled
        try
        {
            while (true))
            {
                // stop if cancellation requested
                cancellationToken.ThrowIfCancellationRequested();

                var taskWebApi = this.CallWebApiAsync(cancellationToken);
                this.activeTasks.Add(taskWebApi);
                await Task.Delay(waitTime, cancellationToken);

                // remove all completed tasks:
                activeTasks.RemoveWhere(task => task.IsCompleted);
            }
        }
        catch (OperationCanceledException exception)
        {
            // caller requested to cancel. Wait until all tasks are finished.
            await Task.WhenAll(this.activeTasks);

            // if desired do some logging for all tasks that were not completed.
        }
    }

И настроенный CallWebApiAsyn c:

    private async Task CallWebApiAsync(CancellationToken cancellationToken)
    {
         const string requestUri = ...
         var httpResponseMessage = await this.httpClient.GetAsync(requestUri, cancellationToken);

         // if here: cancellation not requested
         this.ProcessHttpResponse(httpResponseMessage);
    }

    private void ProcessHttpRespons(HttpResponseMessage httpResponseMessage)
    {
        ...
    }
}

Использование:

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
Test1 test = new Test1();

Task taskCallWebApiRepeatedly = test.StartAsync(cancellationTokenSource.Token);

// because you didn't await, you are free to do other things, while WebApi is called
// every 2 seconds
DoSomethingElse();

// you get bored. Request cancellation:
cancellationTokenSource.Cancel();

// of course you need to await until all tasks are finished:
await Task.Wait(taskCallWebApiRepeatedly);

Поскольку каждый обещает регулярно проверять, запрашивается ли отмена, вы уверены, что в течение разумного времени все задачи завершены, и вычистили свой беспорядок. Определение или «разумное время» является произвольным, но, скажем, менее 100 мсек c?

0 голосов
/ 22 апреля 2020

Если все, что вам нужно, это запускать метод каждые две секунды, то System.Timers.Timer, вероятно, является наиболее подходящим инструментом для использования:

public class Test1
{
    private readonly HttpClient _client;
    private readonly System.Timers.Timer _timer;

    public Test1()
    {
        _client = new HttpClient();
        _timer = new System.Timers.Timer();
        _timer.Interval = 2000;
        _timer.Elapsed += Timer_Elapsed;
    }

    private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        var fireAndForgetTask = CallWebApiAsync();
    }

    private async Task CallWebApiAsync()
    {
        var html = await _client.GetStringAsync("http://example.com");
        //...
    }

    public void Start() => _timer.Start();
    public void Stop() => _timer.Stop();
}
0 голосов
/ 22 апреля 2020

как то так. Кстати, воспринимайте это как псевдокод, когда я печатаю на кровати :))

List<Task> tasks = new List<Task>();
tasks.Add(CallWebApi());

while (! await Task.WhenAny(tasks))
            {
                tasks.Add(CallWebApi());    <=========== this line 
                await Task.Delay(2000);
            }
...