for 循环内的异步 HTTP 请求,无需同时等待异步操作

时间:2021-06-18 01:05:02

标签: c# .net asynchronous

我正在构建一个解决方案,以从 for 循环内的 API 调用中找到所需的值。

我基本上需要将 for 循环的索引传递给 API,其中一个索引将返回所需的输出,在那一刻我希望 for 循环中断,但我需要提高这个过程的效率。我想在没有 await 的情况下使其异步,以便当某些 API 返回所需的输出时,它会中断循环。

每个 API 调用大约需要 10 秒,所以如果我使用异步或多线程,我会大大减少执行时间。

我没有找到关于如何制作异步/不等待 HTTP 请求的任何好的方向。

有什么建议吗?

for (int i = 0; i < 60000; i += 256)
{            
    Console.WriteLine("Incrementing_value: " + i);
    string response = await client.GetStringAsync(
        "http://localhost:7075/api/Function1?index=" + i.ToString());
    Console.WriteLine(response);                
    if (response != "null")//
    {
        //found the desired output
        break;
    }
}

2 个答案:

答案 0 :(得分:1)

您可以并行运行请求,并在找到所需输出后取消它们:

public class Program {

    public static async Task Main() {
        var cts = new CancellationTokenSource();
        var client = new HttpClient();

        var tasks = new List<Task>();

        // This is the number of requests that you want to run in parallel.
        const int batchSize = 10;

        int requestId = 0;
        int batchRequestCount = 0;

        while (requestId < 60000) {
            if (batchRequestCount == batchSize) {
                // Batch size reached, wait for current requests to finish.
                await Task.WhenAll(tasks);
                tasks.Clear();
                batchRequestCount = 0;
            }

            tasks.Add(MakeRequestAsync(client, requestId, cts));
            requestId += 256;
            batchRequestCount++;
        }

        if (tasks.Count > 0) {
            // Await any remaining tasks
            await Task.WhenAll(tasks);
        }
    }

    private static async Task MakeRequestAsync(HttpClient client, int index, CancellationTokenSource cts) {
        if (cts.IsCancellationRequested) {
            // The desired output was already found, no need for any more requests.
            return;
        }

        string response;

        try {
            response = await client.GetStringAsync(
                "http://localhost:7075/api/Function1?index=" + index.ToString(), cts.Token);
        }
        catch (TaskCanceledException) {
            // Operation was cancelled.
            return;
        }

        if (response != "null") {
            // Cancel all current connections
            cts.Cancel();

            // Do something with the output ...
        }
    }

}

请注意,此解决方案使用一种简单的机制来限制并发请求的数量,更高级的解决方案将使用信号量(如某些评论中所述)。

答案 1 :(得分:1)

有多种方法可以解决此问题。我个人最喜欢使用 ActionBlock<T> 库中的 TPL Dataflow 作为处理引擎。此组件为接收到的每个数据元素调用提供的 public $role; public $permissions; public $selectedPermissions = []; protected $listeners = ['eidtRole']; public function mount() { $this->permissions = Permission::get(); } public function eidtRole(Role $role) { $this->role = $role; $this->selectedPermissions = $this->rolePermissions = $role->permissions()->pluck('id')->map(function ($id) { return strval($id); })->toArray(); } 委托,也可以提供异步委托 (Action<T>)。它有许多有用的功能,包括(除其他外)可配置的并行度/并发度,以及通过 Func<T, Task> 取消。下面是一个利用这些功能的实现:

CancellationToken

TPL 数据流库内置于 .NET Core / .NET 5。它可以作为 .NET Framework 的包使用。

即将推出的 .NET 6 将采用新的 API Parallel.ForEachAsync,它也可用于以类似方式解决此问题。