Отправить несколько запросов одновременно в мой WebAPI с помощью Task.WhenAll - PullRequest
0 голосов
/ 18 апреля 2020

Я пытаюсь отправить несколько одинаковых запросов (почти) один раз в мой WebAPI, чтобы провести тестирование производительности. Для этого я звоню PerformRequest несколько раз и жду их, используя await Task.WhenAll.

Я хочу рассчитать время, необходимое для выполнения каждого запроса, плюс время начала каждого из них. Однако в моем коде я не знаю, что произойдет, если результат R3 (запрос № 3) будет раньше, чем R1? Будет ли продолжительность неправильной?

Из того, что я вижу в результатах, я думаю, что результаты смешиваются друг с другом. Например, результат R4 устанавливается как результат R1. Так что любая помощь будет оценена.

GlobalStopWatcher - это класс * stati c, который я использую для определения времени начала каждого запроса.

В основном я хочу убедиться, что 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());
}

1 Ответ

3 голосов
/ 20 апреля 2020

Когда я думаю, что вы ошибаетесь, пытайтесь использовать stati c GlobalStopWatcher, а затем вставлять этот код в тестируемую вами функцию.

Вы должны держать все отдельно и использовать новый экземпляр Stopwatch для каждого RunAllTasks вызова.

Давайте сделаем это так.

Начнем с этих:

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(); захватывает новый Stopwatch для каждого RunAllTasks вызова. Ничто на самом деле больше не является «глобальным».

Если я выполню это с RunAllTasks(7), то получу такой результат:

Dummy Test

Это запускается, и он считает правильно.

Теперь вы можете просто изменить свой метод 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...