web-dev-qa-db-fra.com

Utilisation correcte de async / wait et Task.Run ()

Je développe une application qui lira les fichiers Excel à partir du disque, puis traitera les tests en fonction des données contenues dans les fichiers. Afin d'empêcher l'interface utilisateur de se verrouiller lors du chargement de fichiers et de l'exécution de tests, j'ai implémenté l'async/wait avec Task.Run () pour gérer les charges et les tests en cours d'exécution. Pour le moment, je n'ai qu'un seul test et trois fichiers d'entrée, mais je veux m'assurer que cette application évoluera à mesure que le nombre de fichiers et de tests augmentera. Mes autres préoccupations ne prennent pas trop de puissance de traitement en ayant trop de threads et en pouvant signaler la progression des chargements de fichiers et des tests. Ai-je correctement implémenté async/wait/Task.Run afin de répondre à ces préoccupations?

private async void ProcessTestsAsync()
        {
            try
            {
                //TestFactory testFact = new TestFactory();

                if (WorkingDirectory != null && (from f in Files where f.Location == "" select f).FirstOrDefault() == null)
                {
                    // Load Files
                    var loadResults = await LoadFilesAsync();

                    // Run Tests
                    if (loadResults)
                    {
                        var testResults = await RunTestsAsync();
                        if (testResults)
                        {
                            MessageBox.Show("Success");
                        }
                        else
                        {
                            MessageBox.Show("Failure");
                        }
                    }
                    else
                    {
                        MessageBox.Show("File Load Failed.  Please check your files and try again.");
                    }
                }
                else
                {
                    MessageBox.Show("One or more files has not been selected.  Please choose any missing files and try again.");
                }
            }
            catch (Exception err)
            {
                MessageBox.Show("Tests failed.  Please try again.  Error: " + err.Message);
            }
        }

private async Task<bool> RunTestsAsync()
        {
            try
            {
                using (var sem = new SemaphoreSlim(MAX_THREADS))    // Limit the number of threads that can run at a time
                {
                    TestFactory testFact = new TestFactory();
                    var tasks = new List<Task>();

                    foreach (Model.Test test in IncludedTests)
                    {

                        await sem.WaitAsync();
                        tasks.Add(Task.Run(() =>
                        {
                            SortedList<Enums.FileType, DataTable> sources = new SortedList<Enums.FileType, DataTable>();
                            foreach (Enums.FileType fileType in test.ExpectedSources)
                            {
                                sources.Add(fileType, _files[fileType]);
                            }

                            test.Progress = 25;

                            TestBase t = testFact.getTest(test.Type);

                            if (t.SetWorkingDirectory(WorkingDirectory)) 
                            {
                                test.Progress = 50;
                                if (t.SetSources(sources))
                                {
                                    test.Progress = 75;
                                    if (t.RunTest())
                                    {
                                        test.Progress = 100;
                                    }
                                }                                                              
                            }
                            else
                            {
                                MessageBox.Show("Test Failed.");
                            }
                        }));                        
                    }
                    await Task.WhenAll(tasks);
                }
                    return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        private async Task<bool> LoadFilesAsync()
        {
            try
            {
                _files.Clear();
                using (var sem = new SemaphoreSlim(MAX_THREADS))
                {
                    var tasks = new List<Task>();

                    foreach (var file in Files)
                    {
                        await sem.WaitAsync();  // Limit the number of threads that can run at a time
                        tasks.Add(Task.Run(() =>
                        {
                            file.FileLoadStatus = Enums.FileLoadStatus.InProgress;
                            _files.Add(file.Type, file.Source.LoadRecords(file));
                            file.FileLoadStatus = Enums.FileLoadStatus.Completed;
                        }));

                    }

                    await Task.WhenAll(tasks);
                }
                return true;
            }
            catch (Exception)
            {
                return false;
            }            
        }
4
Tim Hutchison

À première vue, je dirais que le code async/await semble OK. Cependant, il y a quelques points à considérer.

  • Ne modifiez pas l'état global ou de classe dans une tâche. Faire en sorte que la tâche soit une vraie fonction (c'est-à-dire qu'elle n'utilise ni ne modifie l'état global ou de classe). Exemple dans LoadFilesAsync()
  • Lorsque le code à exécuter est suffisamment petit, la surcharge de la tâche est pire que la simple exécution du code sur place (exemple dans LoadFilesAsync()).
  • Je suppose que c'est temporaire, mais MessageBox.ShowMessage() n'est pas une bonne idée au fond de vos tâches (exemple dans RunTestsAsync() lorsque la classe de gestion fait également la même chose)

Dans votre méthode LoadFilesAsync() je la restructurerais comme ceci:

private async Task<IEnumerable<MyFile>> LoadFilesAsync()
{
    // Multiple tasks clearing state can step on each other's toes.
    // It's better to return the set of files and then have the receiver
    // code merge them or do work with it directly.
    List<MyFile> files = new List<MyFile>();

    foreach (var file in Files)
    {
        file.FileLoadStatus = Enums.FileLoadStatus.InProgress;
        // Just await the load here.
        // Since I don't know the type of your _files object
        // this code won't compile since List.Add() only takes one argument
        files.Add(file.Type, await file.Source.LoadRecords(file));
        file.FileLoadStatus = Enums.FileLoadStatus.Completed;
    }

    return files;
}
5
Berin Loritsch