簡體   English   中英

如何在線程中調用異步方法但在 c# 中等待它

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

我有一個線程負責每 2 秒從 4 個網站調用一個 webapi。 不應等待 Webapi 調用方法,因為如果網站不可用,它將等待 5 秒以獲取超時,然后下一個網站調用將被延遲。 由於 .NET 4.7.2 中的 HttpClient 只有 async 方法,它應該與 await 一起使用,如果沒有,編譯器會發出警告,我們可能會出現意外行為(如微軟所說)。 所以我應該使用 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();
            .....
            .....
        }

 }

所以你有一個異步過程,它使用 HttpClient 來獲取一些信息並處理獲取的數據:

async Task CallWebApiAsync() {...}

改進 1 :使用 async 為 async 方法添加后綴是一種很好的做法。 這樣做是為了使異步版本可以存在於執行類似操作的非異步版本旁邊。

在此方法中,您使用 HttpClient 方法之一來獲取信息。 由於 CallWebApiAsync 是可等待的,我假設使用了異步方法(GetAsync、GetStreamAsync 等),並且該方法僅在需要異步方法的結果時才等待。

這樣做的好處是,作為 CallWebApiAsync 的用戶,只要您不等待呼叫,您就可以自由地做其他事情,即使網站沒有反應。 問題是: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 :在 async-await 中總是返回Task而不是voidTask<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 更容易理解它所代表的含義。

如何停止?

問題當然是,在您請求終止后,可能仍有一些任務正在運行。 你必須等待他們完成。 但即便如此:有些任務可能根本無法在合理的時間內完成。

改進 5 :使用 CancellationToken 請求取消。

為了以簡潔的方式取消任務,發明了 class CancellationToken 啟動任務的用戶創建一個CancellationTokenSource object,並向這個 object 請求一個CancellationToken 這個令牌被傳遞給所有異步方法。 一旦用戶想要取消使用此CancellationTokenSource啟動的所有任務,他就會請求CancellationTokenSource取消。 所有擁有來自此來源的令牌的任務都承諾定期檢查令牌以查看是否請求取消。 如果是這樣,該任務會進行一些清理(如果需要)並返回。

一切都總結在一個 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.
        }
    }

以及調整后的 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)
    {
        ...
    }
}

用法:

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 毫秒?

如果您只想每兩秒執行一次方法,那么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();
}

像這樣的東西。 順便說一句,當我坐在床上打字時,把它當作偽代碼:)

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