使用 Task.WhenAll 一次向我的 WebAPI 发送多个请求
Posted
技术标签:
【中文标题】使用 Task.WhenAll 一次向我的 WebAPI 发送多个请求【英文标题】:Send multiple requests at once to my WebAPI using Task.WhenAll 【发布时间】:2020-08-01 05:20:43 【问题描述】:我试图(几乎)一次向我的 WebAPI 发送多个相同的请求以进行一些性能测试。
为此,我多次致电PerformRequest
并使用await Task.WhenAll
等待他们。
我想计算完成每个请求所需的时间加上每个请求的开始时间。但是,在我的代码中,我不知道如果 R3(请求号 3)的结果出现在 R1 之前会发生什么?时长会出错吗?
从我在结果中看到的,我认为结果是相互混合的。例如,R4 的结果集为 R1 的结果。所以任何帮助将不胜感激。
GlobalStopWatcher
是一个静态类,我用它来查找每个请求的开始时间。
基本上我想确保每个请求的elapsedMilliseconds
和Duration
都与请求本身相关联。
因此,如果第 10 次请求的结果在第 1 次请求的结果之前,则持续时间将为duration = elapsedTime(10th)-(startTime(1st))
。不是这样吗?
我想添加一个lock
,但似乎不可能在有await
关键字的地方添加它。
public async Task<RequestResult> PerformRequest(RequestPayload requestPayload)
var url = "myUrl.com";
var client = new RestClient(url) Timeout = -1 ;
var request = new RestRequest Method = Method.POST ;
request.AddHeaders(requestPayload.Headers);
foreach (var cookie in requestPayload.Cookies)
request.AddCookie(cookie.Key, cookie.Value);
request.AddJsonBody(requestPayload.BodyRequest);
var st = new Stopwatch();
st.Start();
var elapsedMilliseconds = GlobalStopWatcher.Stopwatch.ElapsedMilliseconds;
var result = await client.ExecuteAsync(request).ConfigureAwait(false);
st.Stop();
var duration = st.ElapsedMilliseconds;
return new RequestResult()
Millisecond = elapsedMilliseconds,
Content = result.Content,
Duration = duration
;
public async Task RunAllTasks(int numberOfRequests)
GlobalStopWatcher.Stopwatch.Start();
var arrTasks = new Task<RequestResult>[numberOfRequests];
for (var i = 0; i < numberOfRequests; i++)
arrTasks[i] = _requestService.PerformRequest(requestPayload, false);
var results = await Task.WhenAll(arrTasks).ConfigureAwait(false);
RequestsFinished?.Invoke(this, results.ToList());
【问题讨论】:
是什么让你觉得结果好坏参半? @GabrielLuci 假设我们在ForLoop
中触发了 100 个请求。例如,第一个请求的 elapsedMilliseconds
将是 1ms 和最后一个请求的 100ms。但是,第 100 个请求的结果在第一个请求的结果之前。那不是返回第 100 个请求(先到)的结果内容和第一个请求(最后一个)的elapsedMilliseconds
吗?
每个请求都有一个单独的本地Stopwatch
,所有请求都有一个全局Stopwatch
。您将“持续时间”称为本地Stopwatch
获得的测量值,将“毫秒”称为全局Stopwatch
获得的测量值。您担心两种测量中的哪一种可能是错误的,是局部的、全局的还是两者兼而有之?
@TheodorZoulias 他们俩。我想知道该请求在多少毫秒后触发(elapsedMilliseconds
)以及该请求需要多长时间才能完成(duration
)
那你的问题可能太宽泛了。在这里,每个帖子我们只能问一个问题。关于全局Stopwatch
,你可能会觉得这个问题很有趣:Is Stopwatch.ElapsedTicks threadsafe?
【参考方案1】:
我认为你会出错的地方是尝试使用静态 GlobalStopWatcher
,然后将此代码推送到你正在测试的函数中。
您应该将所有内容分开,并为每个 RunAllTasks
调用使用一个新的 Stopwatch
实例。
让我们这样吧。
从这些开始:
public async Task<RequestResult<R>> ExecuteAsync<R>(Stopwatch global, Func<Task<R>> process)
var s = global.ElapsedMilliseconds;
var c = await process();
var d = global.ElapsedMilliseconds - s;
return new RequestResult<R>()
Content = c,
Millisecond = s,
Duration = d
;
public class RequestResult<R>
public R Content;
public long Millisecond;
public long Duration;
现在您可以测试任何符合Func<Task<R>>
签名的内容。
让我们试试这个:
public async Task<int> DummyAsync(int x)
await Task.Delay(TimeSpan.FromSeconds(x % 3));
return x;
我们可以这样设置测试:
public async Task<RequestResult<int>[]> RunAllTasks(int numberOfRequests)
var sw = Stopwatch.StartNew();
var tasks =
from i in Enumerable.Range(0, numberOfRequests)
select ExecuteAsync<int>(sw, () => DummyAsync(i));
return await Task.WhenAll(tasks).ConfigureAwait(false);
请注意,var sw = Stopwatch.StartNew();
行会为每个 RunAllTasks
调用捕获一个新的 Stopwatch
。没有什么是真正的“全球性”了。
如果我使用RunAllTasks(7)
执行该操作,那么我会得到以下结果:
它运行并且计数正确。
现在你可以重构你的 PerformRequest
方法来做它需要做的事情:
public async Task<string> PerformRequest(RequestPayload requestPayload)
var url = "myUrl.com";
var client = new RestClient(url) Timeout = -1 ;
var request = new RestRequest Method = Method.POST ;
request.AddHeaders(requestPayload.Headers);
foreach (var cookie in requestPayload.Cookies)
request.AddCookie(cookie.Key, cookie.Value);
request.AddJsonBody(requestPayload.BodyRequest);
var response = await client.ExecuteAsync(request);
return response.Content;
运行测试很容易:
public async Task<RequestResult<string>[]> RunAllTasks(int numberOfRequests)
var sw = Stopwatch.StartNew();
var tasks =
from i in Enumerable.Range(0, numberOfRequests)
select ExecuteAsync<string>(sw, () => _requestService.PerformRequest(requestPayload));
return await Task.WhenAll(tasks).ConfigureAwait(false);
如果对Stopwatch
的线程安全性有任何疑问,那么您可以这样做:
public async Task<RequestResult<R>> ExecuteAsync<R>(Func<long> getMilliseconds, Func<Task<R>> process)
var s = getMilliseconds();
var c = await process();
var d = getMilliseconds() - s;
return new RequestResult<R>()
Content = c,
Millisecond = s,
Duration = d
;
public async Task<RequestResult<int>[]> RunAllTasks(int numberOfRequests)
var sw = Stopwatch.StartNew();
var tasks =
from i in Enumerable.Range(0, numberOfRequests)
select ExecuteAsync<int>(() => lock (sw) return sw.ElapsedMilliseconds; , () => DummyAsync(i));
return await Task.WhenAll(tasks).ConfigureAwait(false);
【讨论】:
不错的答案。不过,在不同步的情况下从多个线程访问非线程安全类的实例属性ElapsedMilliseconds
似乎有风险。
@TheodorZoulias - 不是根据链接:***.com/questions/6664538/…。那里有很多答案说,只要您不启动、停止或重新启动Stopwatch
,它就是安全的。我想我可以用Func<long>
把它包起来,然后把它锁起来。
@Enigmativity 非常好的答案。一个问题。 lock
那里是因为 Task.WhenAll
并行运行所有任务吗?
@MehrdadKamelzadeh - lock
存在是因为我们希望确保对 Stopwatch
的访问是线程安全的。尽管如此,Task.WhenAll
不会创建或使用任何线程。以防万一此代码被更改,以便ExecAsync
(或更深层次的东西)使用其他线程。以上是关于使用 Task.WhenAll 一次向我的 WebAPI 发送多个请求的主要内容,如果未能解决你的问题,请参考以下文章
c# Task.WhenAll(tasks) 和 SemaphoreSlim - 如何知道所有任务何时已完全完成
Parallel.ForEach 与 Task.Run 和 Task.WhenAll
从 Task.WhenAll 调用的异步方法使用 DbContext 并返回错误
Task CancellationTokenSource和Task.WhenAll的应用
csharp Task.WhenAllを利用している场合のタイムアウト处理の书き方(Task.WhenAll,Task.WhenAny,Task.Delay)
Task.WhenAll(taskList).Wait() 是不是与 Task.WaitAll(taskList) 相同?