ディスクからExcelファイルを読み取り、ファイル内のデータに基づいてテストを処理するアプリケーションを開発しています。ファイルをロードしてテストを実行するときにユーザーインターフェイスがロックしないようにするために、Task.Run()でasync/awaitを実装して、実行中のロードとテストを処理しました。現在、1つのテストと3つの入力ファイルしかありませんが、ファイルとテストの数が増えるにつれて、このアプリケーションが確実にスケーリングされるようにしたいと思います。私の他の懸念は、スレッドが多すぎてファイルのロードとテストの進行状況を報告できるため、処理能力を過度に消費していないことです。これらの懸念を満たすために、async/await/Task.Runを正しく実装しましたか?
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;
}
}
一見すると、async
/await
コードは問題ないように見えます。ただし、考慮すべき点がいくつかあります。
LoadFilesAsync()
の例LoadFilesAsync()
の例)。MessageBox.ShowMessage()
はタスクの深いところでは良い考えではありません(処理クラスも同じことを行うRunTestsAsync()
の例)LoadFilesAsync()
メソッドでは、次のように再構築します。
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;
}