ちゃんとしたシステムを作りたい場合インスタンスというもののライフサイクルをきちんと把握しておかなければいけません。
プログラムは目に見えないものなので、インスタンスがどこで作成されて、どこで解放されるのか、それが把握できなくなるということは極端な話ですが制御不能だといってもいいでしょう。
例えばデザインパターンではインスタンス生成に関するパターンという区分けがありまして、代表的なもので言えばSingletonとかFactory(AbstractFactoryやFactoryMethod)がありますね。
今回私が作っていたシステムではスレッドの実行とインスタンス生成を組み合わせた処理がありました。
プログラムの全貌を話すことはできませんが、搔い摘むと下記のようなイメージです。
class TaskManager
{
private CancellationTokenSource source;
public void Create(ISomethingProcess proc){
source = new CancellationTokenSource();
var token = source.Token;
Task.Run(async() =>{
try{
while(!token.IsCancellationRequested){
//何かをする処理 ...
proc.SomeMethod();
await Task.Delay(100, token);//ちょっと待ってみたり
}
catch(TaskCanceledException e){
logger.Info("キャンセルされました",e);
}
catch(Exception e){
logger.Error("致命的なエラーが発生しました。",e);
throw;
}
},token);
}
public void Cancel(){
source.Cancel();
}
}
class TaskList
{
public TaskList(IFactory factory){
list = new List<TaskManager>();
this.factory = factory;
}
private List<TaskManager> list;
private IFactory factory;
public void Start(){
Stop();
list.AddRange(factory.CreateTaskList());
foreach(var each in list){
each.Create(factory.GetSomethings());
}
}
public void Stop(){
foreach(var each in list){
each.Cancel();
}
list.Clear();
}
}
TaskListはコマンドによってタスクを起動したり、停止したりします。
TaskManagerはTaskを生成したり、キャンセルしたりします。
今回TaskListはいろんな処理からStartしたりStopしたりをしてまして、横着して以下のようなコードを書いてしまってました。
class Something
{
public void Hoge(){
tasklist = new TaskList(factory);
tasklist.Start(); // まとめて実行
}
public void Fuga(){
tasklist?.Stop();
}
TaskList tasklist;
}
TaskList自体には何も問題ないかもしれませんがSomethingクラスではtasklistのインスタンスをHogeが実行されるたびに再生成し、スタートをしています。
私はTaskListを作る時、「StopしてからStartしてればタスクがキャンセルされるので制御できるよね!」という考えのもと作ってたんですが
Somethingを作ってた時は、「TaskList再生成すればGCされるだろうし、再生成すればいいよね~」と考えてた感じです。
この処理で、2回3回連続してHogeを実行してしまうと、見事に以前インスタンス化したTaskListが宙ぶらりんになってしまい、キャンセルできないタスクが実行しっぱなしという現象に見舞われたのでした。
今回は全部自分で作成しているシステムでしたのでもう惑わされませんが、これがTaskListとSomething、分業して開発している場合は普通に起こりえる問題ですよね。怖い怖い。