Uso corretto di async / await e Task.Run ()

3

Sto sviluppando un'applicazione che legge i file excel dal disco e quindi elabora i test in base ai dati nei file. Per mantenere l'interfaccia utente bloccata durante il caricamento di file e l'esecuzione di test, ho implementato async / await con Task.Run () per gestire carichi e test in esecuzione. Al momento ho solo un test e tre file di input, ma voglio assicurarmi che questa applicazione sia scalabile come il numero di file e amp; i test aumentano Le altre mie preoccupazioni non stanno assorbendo troppa potenza di elaborazione avendo troppi thread e potendo riportare lo stato di avanzamento dei carichi e dei test dei file. Ho implementato correttamente async / await / Task.Run per soddisfare questi dubbi?

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;
            }            
        }
    
posta Tim Hutchison 01.05.2017 - 14:56
fonte

1 risposta

4

A prima vista, direi che il codice async / await sembra OK. Tuttavia, ci sono alcune cose da considerare.

  • Non modificare lo stato globale o di classe in un'attività. Avere il compito essere una vera funzione (cioè non utilizzare o modificare lo stato globale o di classe). Esempio in LoadFilesAsync()
  • Quando il codice da eseguire è abbastanza piccolo, l'overhead dell'attività è peggiore del semplice esecuzione del codice in atto (esempio in LoadFilesAsync() ).
  • Presumo che sia temporaneo, ma MessageBox.ShowMessage() non è una buona idea nelle tue attività (esempio in RunTestsAsync() quando anche la classe di gestione fa la stessa cosa)

Nel tuo metodo LoadFilesAsync() lo ristrutturerei in questo modo:

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;
}
    
risposta data 01.05.2017 - 15:20
fonte