Tasks.WhenAll(tasks) не перехватывает никаких исключений

Я новичок в C# и Windows Forms.

Я использую библиотеку syncfusion C# для шифрования/дешифрования файлов Word.

Я использую задачи Async/Await для массового расшифрования файлов.

У меня нет проблем зашифровать/расшифровать один файл или зашифровать несколько файлов.

Я столкнулся с проблемой при попытке расшифровать несколько файлов слов или массово.

При расшифровке одного файла с неверным паролем получаю исключение и все работает нормально.

Но во время массового дешифрования файлов с использованием await Task.WhenAll(tasks) внутри блока try-catch я не получаю никаких исключений, в то время как из первой задачи (первого файла для расшифровки) я должен получить исключение, потому что пароль неверен.

В течение всей недели я везде пробовал большинство предложенных решений, но безуспешно и не знаю, что мне делать.

Большое спасибо!

Код должен генерировать исключение, если первая задача не выполнена, но это не так.

GetPathOrExtention gte = new GetPathOrExtention();

private void DecryptDOCXFiles(string fileName)
{
    FileStream inputStream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
    WordDocument document = new WordDocument(inputStream, FormatType.Docx, TbDecPwd1.Text);

    FileStream outputStream = new FileStream(gte.GetDirPath(fileName) + "\\" + gte.GetfileName(fileName).Replace("Encrypted-", ""), FileMode.Create, FileAccess.Write);
    document.Save(outputStream, FormatType.Docx);
    inputStream.Close();
    outputStream.Close();
}

private async Task DecryptWordFiles()
{
    OpenFileDialog ofd = new OpenFileDialog();
    ofd.Filter = "docx files (*.docx)|*.docx|All files (*.*)|*.*";
    ofd.Multiselect = true;
    var tasks = new List<Task>();

    if (TbDecPwd1.Text != "" && ofd.ShowDialog() == DialogResult.OK)
    {
        foreach (string file in ofd.FileNames)
        {
            if (gte.GetFileExtension(file) != ".docx")
            {
                LblProceeding.ResetText();
                MessageBox.Show("Files version is not supported!" + '\n' + '\n' + "Only Word version 2019 and above are supported!", "P-GEN", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            if (gte.GetFileExtension(file) == ".docx")
            {
                LblProceeding.Text = "Proceeding... Do not exit the software!";
                var task = Task.Run(() => DecryptDOCXFiles(file)).ContinueWith(p => progressBar1.PerformStep());
                tasks.Add(task);
            }
        }
    }
    try
        {
            await Task.WhenAll(tasks);  
            LblProceeding.Text = "Done!";
            MessageBox.Show("Files Successfully Decrypted!", "P-GEN", MessageBoxButtons.OK, MessageBoxIcon.Information);
            LblProceeding.ResetText();
            progressBar1.Visible = false;     
        }

    catch (AggregateException)
    {
        LblProceeding.ResetText();
        MessageBox.Show("Ensure the password is correct!" + '\n' + '\n' + "Ensure the files you want to decrypt are not opened in another software!", "P-GEN", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

    catch (Exception)
    {
        LblProceeding.ResetText();
        MessageBox.Show("Ensure the password is correct!" + '\n' + '\n' + "Ensure the files you want to decrypt are not opened in another software!", "P-GEN", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }           
}

private void BtnWordFilesDec_Click(object sender, EventArgs e)
{
    _ = DecryptWordFiles();
}

Пожалуйста, предоставьте минимальный воспроизводимый пример, который вообще не требует выполнения каких-либо операций ввода-вывода или использования пользовательского интерфейса. Просто консольное приложение с одной задачей с ошибкой и другими задачами без ошибок, показывающее Tasks.WhenAll возврат задачи, которая в конечном итоге не привела к ошибке.

Jon Skeet 01.06.2024 17:42

(Вы уверены, что проблема не в том, что вы используете Task.ContinueWith и продолжение не является ошибочным, даже если исходная задача ошибочна?)

Jon Skeet 01.06.2024 17:43

Да, я уверен, что Task.ContinueWith не является проблемой, потому что он работает нормально, когда я ввожу правильный пароль.

HB21 01.06.2024 17:45

Это не значит, что это не проблема, которая мешает вам увидеть ошибки в исходных задачах. Пробовали без звонка ContinueWith?

Jon Skeet 01.06.2024 17:58

Теперь я предоставил консольное приложение, которое довольно убедительно доказывает, что проблема в Task.ContinueWith.

Jon Skeet 01.06.2024 18:11
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
120
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в том, что задачи, которые вы ожидаете, являются результатом звонка Task.ContinueWith, и все они выполняются без ошибок. Вас не волнует статус продолжения — вас волнуют статусы исходных задач.

Вот консольное приложение, демонстрирующее то же поведение:

var tasks = Enumerable.Range(0, 10)
    .Select(ComputeDivision)
    .Select(t => t.ContinueWith(t => Console.WriteLine($"Task status: {t.Status}")))
    .ToList();

await Task.WhenAll(tasks);
Console.WriteLine("All tasks completed");

async Task ComputeDivision(int index)
{
    await Task.Delay(TimeSpan.FromSeconds(index + 1));
    Console.WriteLine($"Index: {index}; Division: {100 / index}");
}

Как написано, это завершает и печатает «Все задачи выполнены». Если вместо этого вы закомментируете вызов ContinueWith, вы увидите ошибку. (т. е. он будет печатать завернутый DivideByZeroException, а не «Все задачи выполнены».)

Аналогично, если вы удалите вызов ContinueWith, вы будете ждать завершения исходных задач, и результирующая задача в конечном итоге окажется ошибочной, если какая-либо из исходных задач окажется ошибочной.

Вы можете изменить продолжение на неудачное, если предыдущая задача не удалась, вызвав для него Wait - поэтому в вашем коде это будет:

.ContinueWith(p => { progressBar1.PerformStep(); p.Wait(); })

По общему признанию, это несколько некрасиво... но и использование Task.Run здесь, по моему мнению, тоже. (Я бы для начала все сделал с асинхронным вводом-выводом, сделав DecryptDOCXFiles асинхронным, если возможно.)

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

Большое спасибо, Джон!! Пусть Бог предложит вам всю необходимую помощь!!

HB21 01.06.2024 18:20

Другие вопросы по теме