У меня есть следующая функция для отправки нескольких SMS:

BulkSMSSenderResult bulkResult = new BulkSMSSenderResult();

if (BulkRequest.Requests.Any()) {
    IEnumerable<(SMSSenderRequest, Task<Nito.Try<SMSSenderResult>>)> sendSmsTasks 
        = BulkRequest.Requests.Select(request => (request, SendSingleSmsAsync(request)));
    await Task.WhenAll(sendSmsTasks.Select(task => task.Item2));

    sendSmsTasks.ToList()
        .ForEach(task => {
            (SMSSenderRequest request, Task<Nito.Try<SMSSenderResult>> tryResult) = task;
            _ = tryResult.Result.Match<Either<ErrorMessage, SMSSenderResult>>(
                exception => new ErrorMessage(exception, request),
                value => value
            )
            .Match(
                result => bulkResult.Add(result),
                error => bulkResult.Add(error)
            );
        });
}

if (BulkRequest.BadRequests.Any()) {
    bulkResult.InvalidRequests = BulkRequest.BadRequests;
}

WriteResponseAsync(context, StatusCodes.Status207MultiStatus, bulkResult);

Это работает почти , как и предполагалось, за исключением того, что кажется, что все SMS отправляются дважды.

Я думаю, проблема может быть в этой строке:

await Task.WhenAll(sendSmsTasks.Select(task => task.Item2));

Я ожидал, что эта строка должна проверять, что SMS-сообщения были отправлены, чтобы код, который идет после, выполнялся безопасно.

Однако кажется, что либо эта строка, либо следующий за ней код оба вызывают выполнение SendSingleSmsAsync(request) ... либо что-то еще (о чем я не могу предположить) вызывает выполнение задания выстрелить дважды (и я уверен, что сам SendSingleSmsAsync(request) работает правильно).

Любые идеи, как это исправить?

0
Brian Kessler 17 Сен 2020 в 22:36

2 ответа

Лучший ответ

Ваша проблема в этом заявлении:

IEnumerable<(SMSSenderRequest, Task<Nito.Try<SMSSenderResult>>)> sendSmsTasks 
                = BulkRequest.Requests.Select(request => (request, SendSingleSmsAsync(request)));

В Select(request => (request, SendSingleSmsAsync(request)) Select - это проекция, и поэтому она оценивается каждый раз, когда вы перечисляете этот запрос.

Другими словами, у вас будет новая (request, SendSingleSmsAsync(request)) пара в следующих местах:

  • sendSmsTasks.Select(task => task.Item2) и
  • sendSmsTasks.ToList()

Поскольку SendSingleSmsAsync - это вызов метода, который возвращает Task, вы в конечном итоге делаете все дважды.

Вы можете легко решить проблему с помощью всего:

var sendSmsTasks = BulkRequest.Requests
    .Select(request => (request, SendSingleSmsAsync(request)))
    .ToArray();

Но я настоятельно рекомендую вам внимательно изучить свой код, потому что он становится очень сложным. Кроме того, выполнение .ToList().ForEach() - это пустая трата ресурсов (память для дополнительного распределения, время для дополнительного цикла), поэтому замените это простым foreach

2
Camilo Terevinto 17 Сен 2020 в 19:47

Вы должны думать об операциях Linq над перечисляемыми объектами больше как о настройке небольших мини-программ для выполнения, чем о том, чтобы их выполнять и запускать.

В вашем случае вы настраиваете вычисление здесь:

IEnumerable<(SMSSenderRequest, Task<Nito.Try<SMSSenderResult>>)> sendSmsTasks 
                    = BulkRequest.Requests.Select(request => (request, SendSingleSmsAsync(request)))

Но затем вы на самом деле проходите и выполняете его дважды - здесь:

await Task.WhenAll(sendSmsTasks.Select(task => task.Item2));

И здесь:

sendSmsTasks.ToList()

Исправление состояло бы в том, чтобы «материализовать» перечисляемое как можно скорее, чтобы с этого момента вы имели дело с реальными данными, а не с цепочкой, ленивым, потенциальным чем-то.

Попробуйте вставить .ToArray() в конец объявления перечисляемого объекта.

2
Jason Holloway 17 Сен 2020 в 20:48