简体   繁体   English

如何在线程中调用异步方法但在 c# 中等待它

[英]How to call async method in a thread but wait for it in c#

I have a thread which is responsible for calling a webapi from 4 websites exactly every 2 seconds.我有一个线程负责每 2 秒从 4 个网站调用一个 webapi。 The Webapi call method should not be awaited because if a website is not available it will wait 5 second to get timeout and then the next website call will be delayed.不应等待 Webapi 调用方法,因为如果网站不可用,它将等待 5 秒以获取超时,然后下一个网站调用将被延迟。 As HttpClient in .NET 4.7.2 has only async methods, it should be used with await, and if not, compiler gives warning and we may get unexpected behavior (as Microsoft says).由于 .NET 4.7.2 中的 HttpClient 只有 async 方法,它应该与 await 一起使用,如果没有,编译器会发出警告,我们可能会出现意外行为(如微软所说)。 So should I use Task.Run or call Threadpool.QueueUserWorkItem to make a webapi call in parallel.所以我应该使用 Task.Run 还是调用 Threadpool.QueueUserWorkItem 来并行进行 webapi 调用。 Here is sudocode:这是须藤代码:

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

 }

So you have an async procedure that uses a HttpClient to fetch some information and process the fetched data:所以你有一个异步过程,它使用 HttpClient 来获取一些信息并处理获取的数据:

async Task CallWebApiAsync() {...}

Improvement 1 : it is good practice to suffix async methods with async.改进 1 :使用 async 为 async 方法添加后缀是一种很好的做法。 This is done to make it possible to let an async version exist next to a non-async version that does something similarly.这样做是为了使异步版本可以存在于执行类似操作的非异步版本旁边。

Inside this method you are using one of the HttpClient methods to fetch the information.在此方法中,您使用 HttpClient 方法之一来获取信息。 As CallWebApiAsync is awaitable, I assume the async methods are used (GetAsync, GetStreamAsync, etc), and that the method only awaits when it needs the result of the async method.由于 CallWebApiAsync 是可等待的,我假设使用了异步方法(GetAsync、GetStreamAsync 等),并且该方法仅在需要异步方法的结果时才等待。

The nice thing about this is, that as a user of CallWebApiAsync, as long as you don't await the call, you are free to do other things, even if the website isn't reacting.这样做的好处是,作为 CallWebApiAsync 的用户,只要您不等待呼叫,您就可以自由地做其他事情,即使网站没有反应。 The problem is: after 2 seconds, you want to call the method again.问题是:2秒后,你想再次调用该方法。 But what to do if the method hasn't finished yet.但是如果方法还没有完成怎么办。

Improvement 2 Because you want to be able to start a new Task, while the previous one has not finished: remember the started tasks, and throw them away when finished.改进 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);        
}

It might be handy to remove all completed tasks at once:一次删除所有已完成的任务可能会很方便:

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

Now we can adjust your ProcThread.现在我们可以调整您的 ProcThread。

Improvement 3 : in async-await always return Task instead of void and Task<TResult> instead of TResult .改进 3 :在 async-await 中总是返回Task而不是voidTask<TResult>而不是TResult Only exception: eventhandlers return void .唯一的例外:事件处理程序返回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();            
    }
}

Improvement 4 : Use TimeSpan.改进 4 :使用 TimeSpan。

TimeSpan.FromSeconds(2) is much easier to understand what it represents than a value 2000. TimeSpan.FromSeconds(2)比值 2000 更容易理解它所代表的含义。

How to stop?如何停止?

The problem is of course, after you request termination there might still be some tasks running.问题当然是,在您请求终止后,可能仍有一些任务正在运行。 You'll have to wait for them to finish.你必须等待他们完成。 But even then: some tasks might not finish at all within reasonable time.但即便如此:有些任务可能根本无法在合理的时间内完成。

Improvement 5 : use CancellationToken to request cancellation.改进 5 :使用 CancellationToken 请求取消。

To cancel tasks in a neat way, class CancellationToken is invented.为了以简洁的方式取消任务,发明了 class CancellationToken Users who start a task create a CancellationTokenSource object, and ask this object for a CancellationToken .启动任务的用户创建一个CancellationTokenSource object,并向这个 object 请求一个CancellationToken This token is passed to all async methods.这个令牌被传递给所有异步方法。 As soon as the user wants to cancel all tasks that were started using this CancellationTokenSource , he requests the CancellationTokenSource to cancel.一旦用户想要取消使用此CancellationTokenSource启动的所有任务,他就会请求CancellationTokenSource取消。 All tasks that have a token from this source have promised to regularly check the token to see if cancellation is requested.所有拥有来自此来源的令牌的任务都承诺定期检查令牌以查看是否请求取消。 If so, the task does some cleanup (if needed) and returns.如果是这样,该任务会进行一些清理(如果需要)并返回。

Everything summarized in one class:一切都总结在一个 class 中:

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.
        }
    }

And the adjusted CallWebApiAsync:以及调整后的 CallWebApiAsync:

    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)
    {
        ...
    }
}

Usage:用法:

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

Because everyone promises to check regularly if cancellation is requested, you are certain that within reasonable time all tasks are finished, and have cleaned up their mess.因为每个人都承诺定期检查是否要求取消,所以您可以确定在合理的时间内完成所有任务,并清理他们的烂摊子。 The definition or "reasonable time" is arbitrary, but let's say, less than 100 msec?定义或“合理时间”是任意的,但假设小于 100 毫秒?

If all you want is to execute a method every two seconds, then aSystem.Timers.Timer is probably the most suitable tool to use:如果您只想每两秒执行一次方法,那么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();
}

something like this.像这样的东西。 BTW take this as pseudo code as I am typing sitting on my bed:)顺便说一句,当我坐在床上打字时,把它当作伪代码:)

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM