これが興味深い図書館作家のジレンマです。私のライブラリ(私の場合はEasyNetQ)で、スレッドローカルリソースを割り当てています。したがって、クライアントが新しいスレッドを作成してから、ライブラリの特定のメソッドを呼び出すと、新しいリソースが作成されます。EasyNetQの場合、クライアントが新しいスレッドで「公開」を呼び出すと、RabbitMQサーバーへの新しいチャネルが作成されます。リソース(チャネル)をクリーンアップできるように、クライアントスレッドがいつ終了するかを検出できるようにしたい。
私が思いついたこれを行う唯一の方法は、クライアントスレッドへのJoin呼び出しをブロックするだけの新しい「ウォッチャー」スレッドを作成することです。ここに簡単なデモンストレーションがあります:
最初に私の「ライブラリ」。クライアントスレッドを取得してから、「参加」をブロックする新しいスレッドを作成します。
public class Library
{
public void StartSomething()
{
Console.WriteLine("Library says: StartSomething called");
var clientThread = Thread.CurrentThread;
var exitMonitorThread = new Thread(() =>
{
clientThread.Join();
Console.WriteLine("Libaray says: Client thread existed");
});
exitMonitorThread.Start();
}
}
これが私のライブラリを使用するクライアントです。新しいスレッドを作成してから、ライブラリのStartSomethingメソッドを呼び出します。
public class Client
{
private readonly Library library;
public Client(Library library)
{
this.library = library;
}
public void DoWorkInAThread()
{
var thread = new Thread(() =>
{
library.StartSomething();
Thread.Sleep(10);
Console.WriteLine("Client thread says: I'm done");
});
thread.Start();
}
}
このようにクライアントを実行すると、次のようになります。
var client = new Client(new Library());
client.DoWorkInAThread();
// give the client thread time to complete
Thread.Sleep(100);
私はこの出力を取得します:
Library says: StartSomething called
Client thread says: I'm done
Libaray says: Client thread existed
だからそれは動作しますが、それは醜いです。これらすべてのブロックされたウォッチャースレッドがぶら下がっているという考えは本当に好きではありません。これを行うためのより良い方法はありますか?
最初の選択肢。
IDisposableを実装するワーカーを返すメソッドを提供し、スレッド間でワーカーを共有してはならないことをドキュメントで明確にします。変更されたライブラリは次のとおりです。
public class Library
{
public LibraryWorker GetLibraryWorker()
{
return new LibraryWorker();
}
}
public class LibraryWorker : IDisposable
{
public void StartSomething()
{
Console.WriteLine("Library says: StartSomething called");
}
public void Dispose()
{
Console.WriteLine("Library says: I can clean up");
}
}
クライアントはもう少し複雑になりました。
public class Client
{
private readonly Library library;
public Client(Library library)
{
this.library = library;
}
public void DoWorkInAThread()
{
var thread = new Thread(() =>
{
using(var worker = library.GetLibraryWorker())
{
worker.StartSomething();
Console.WriteLine("Client thread says: I'm done");
}
});
thread.Start();
}
}
この変更の主な問題は、APIの重大な変更であるということです。既存のクライアントを書き直す必要があります。今ではそれほど悪いことではありませんが、それは彼らを再訪し、彼らが正しくクリーンアップしていることを確認することを意味します。
ノーブレークの2番目の選択肢。APIは、クライアントが「作業範囲」を宣言する方法を提供します。スコープが完了すると、ライブラリはクリーンアップできます。ライブラリはIDisposableを実装するWorkScopeを提供しますが、上記の最初の選択肢とは異なり、StartSomethingメソッドはLibraryクラスに残ります。
public class Library
{
public WorkScope GetWorkScope()
{
return new WorkScope();
}
public void StartSomething()
{
Console.WriteLine("Library says: StartSomething called");
}
}
public class WorkScope : IDisposable
{
public void Dispose()
{
Console.WriteLine("Library says: I can clean up");
}
}
クライアントは、StartSomething呼び出しをWorkScopeに入れるだけです...
public class Client
{
private readonly Library library;
public Client(Library library)
{
this.library = library;
}
public void DoWorkInAThread()
{
var thread = new Thread(() =>
{
using(library.GetWorkScope())
{
library.StartSomething();
Console.WriteLine("Client thread says: I'm done");
}
});
thread.Start();
}
}
これは、ライブラリユーザーにスコープについて考える必要がないため、最初の選択肢よりも好きではありません。