Почему мой цикл for увеличивается после того, как он должен остановиться?

#for-loop #async-await #asp.net-core-mvc #visual-studio-2019

#for-loop #async-await #asp.net-core-mvc #visual-studio-2019

Вопрос:

Я пытаюсь увеличить скорость загрузки файлов в моем приложении, загружая их параллельно. Раньше я загружал их последовательно, и все работало нормально, но когда я попытался загрузить их параллельно, я столкнулся с необъяснимыми проблемами.

Вот мой метод, с помощью которого я последовательно загружал файлы:

         public IActionResult DownloadPartFiles([FromBody] FileRequestParameters parameters)
        {
            List<InMemoryFile> files = new List<InMemoryFile>();
            for (int i = 0; i < parameters.FileNames.Length; i  )
            {
                InMemoryFile inMemoryFile = GetInMemoryFile(parameters.FileLocations[i], parameters.FileNames[i]).Resu<
                files.Add(inMemoryFile);
            }
            byte[] archiveFile = null;
            using (MemoryStream archiveStream = new MemoryStream())
            {
                using (ZipArchive archive = new ZipArchive(archiveStream, ZipArchiveMode.Create, true))
                {
                    foreach (InMemoryFile file in files)
                    {
                        ZipArchiveEntry zipArchiveEntry = archive.CreateEntry(file.FileName, CompressionLevel.Optimal);
                        using (MemoryStream originalFileStream = new MemoryStream(file.Content))
                        using (Stream zipStream = zipArchiveEntry.Open())
                        {
                            originalFileStream.CopyTo(zipStream);
                        }
                    }
                }
                archiveFile = archiveStream.ToArray();
            }
            return File(archiveFile, "application/octet-stream");
        }
 

Вот метод, измененный для параллельной загрузки файлов:

         public async Task<IActionResult> DownloadPartFiles([FromBody] FileRequestParameters parameters)
        {
            List<Task<InMemoryFile>> fileTasks = new List<Task<InMemoryFile>>();
            for (int i = 0; i < parameters.FileNames.Length; i  )
            {
                if(i == parameters.FileNames.Length - 1)
                {
                    int breakpoint = 0;
                }
                if(i == parameters.FileNames.Length)
                {
                    int breakpoint = 0;
                }
                fileTasks.Add(Task.Run(() => GetInMemoryFile(parameters.FileLocations[i], parameters.FileNames[i])));
            }
            InMemoryFile[] fileResults = await Task.WhenAll(fileTasks);
            byte[] archiveFile = null;
            using (MemoryStream archiveStream = new MemoryStream())
            {
                using (ZipArchive archive = new ZipArchive(archiveStream, ZipArchiveMode.Create, true))
                {
                    foreach (InMemoryFile file in fileResults)
                    {
                        ZipArchiveEntry zipArchiveEntry = archive.CreateEntry(file.FileName, CompressionLevel.Optimal);
                        using (MemoryStream originalFileStream = new MemoryStream(file.Content))
                        using (Stream zipStream = zipArchiveEntry.Open())
                        {
                            originalFileStream.CopyTo(zipStream);
                        }
                    }
                }
                archiveFile = archiveStream.ToArray();
            }
            return File(archiveFile, "application/octet-stream");
        }
 

Вот метод, который выполняет фактическую загрузку:

         private async Task<InMemoryFile> GetInMemoryFile(string fileLocation, string fileName)
        {
            InMemoryFile file;
            using (HttpClient client = new HttpClient())
            using (HttpResponseMessage response = await client.GetAsync(fileLocation))
            {
                byte[] fileContent = await response.Content.ReadAsByteArrayAsync();
                file = new InMemoryFile(fileName, fileContent);
            }
            return file;
        }
 

Теперь проблемы, с которыми я сталкиваюсь, заключаются в том, что после того, как я изменил DownloadPartFiles, чтобы получать все файлы параллельно, мой цикл for теперь, похоже, выходит за пределы своего состояния остановки. Например, параметры if.Имена файлов.Длина возвращает 12 цикл for не должен выполняться, когда i = 12, и он должен выйти из цикла. Однако в моем тестировании он будет продолжать выполняться при i = 12, и, как и следовало ожидать, я столкнулся с ошибкой out of bounds. Я попытался установить точки останова в своем коде, чтобы убедиться, что он действительно выполняется после условия остановки, и возникло более странное поведение. В мой цикл for я включил два оператора if с переменными точки останова для прерывания. Он всегда будет прерываться, когда я должен быть в его последнем цикле, но никогда не прерывается, когда я один после ожидаемого последнего цикла. Кажется, что эта точка останова пропускается, когда я превышаю ожидаемое условие остановки. Он будет работать нормально, если я пройдусь по коду во время отладки, но выйдет за пределы ошибки, когда я позволю ему работать нормально.

Я не уверен, почему это происходит, но я все еще новичок в асинхронном программировании, так что, возможно, это просто недосмотр где-то. Дайте мне знать, если мне нужно что-нибудь еще объяснить.

Комментарии:

1. Насколько вы в этом уверены parameters.FileLocations.Length == parameters.FileNames.Length ?

2. Да, я действительно обнаружил проблему и опубликую ответ.

Ответ №1:

Я совершаю критическую ошибку, поскольку я попытался обернуть асинхронный метод (мой метод GetInMemoryFile) в метод Task.Run(), который используется для обертывания синхронных методов, чтобы заставить их работать асинхронно. Это вызвало странное поведение.

Короче говоря, я изменил

 fileTasks.Add(Task.Run(() => GetInMemoryFile(parameters.FileLocations[i], parameters.FileNames[i])));
 

Для

 fileTasks.Add(GetInMemoryFile(parameters.FileLocations[i], parameters.FileNames[i]));