新しいスレッドを開始するときは、コンテナにまったく新しいオブジェクト グラフを作成させるのが賢明です。HTTP リクエストのコンテキスト (またはスレッド コンテキストなど) で作成された依存関係を再利用すると、競合状態やその他の種類の障害や奇妙な動作が発生する可能性があります。
もちろん、これらの依存関係がスレッドセーフである (または要求ごとの有効期間で作成され、非同期操作をトリガーした後に要求によって使用されない) ことがわかっている場合は、必ずしもそうである必要はありませんが、これは問題ではありません。これらの依存関係の消費者は、すべてを結び付けるアプリケーションの部分 (コンポジション ルート)の責任であるため、知っているか依存する必要があります。これを行うと、後でその依存関係の構成を変更することも難しくなります。
代わりに、メソッドを独自のクラスにリファクタリングDoThings
し、コントローラーを何らかのIDoThings
インターフェイスに依存させる必要があります。このようにして、すべてを結び付ける瞬間まで、非同期処理に関する決定を延期できます。別の種類のアプリケーション (Windows サービスなど) でそのロジックを再利用する場合、この操作を非同期で実行する方法を変更することもできます (または単に同期的に実行することもできます)。
さらに一歩進んで、実際のDoThings
ビジネス ロジックとそのロジックを非同期的に実行する部分は、2 つの異なる関心事であり、2 つの異なる責任です。それらを異なるクラスに分ける必要があります。
これが私があなたにアドバイスすることの例です:
インターフェイスを定義します。
public interface IDoThings
{
void DoThings();
}
コントローラーをそのインターフェースに依存させます。
public class SomeController : Controller
{
private readonly IDoThings thingsDoer;
public SomeController(IDoThings thingsDoer)
{
this.thingsDoer = thingsDoer;
}
public ActionResult DoThings()
{
this.thingsDoer.DoThings();
return View();
}
}
ビジネス ロジックを含む実装を定義します。
public class DoingThings : IDoThings
{
private readonly ICommandBus commandBus;
private readonly IObjectRepository objectRepository;
public DoingThings(ICommandBus commandBus,
IObjectRepository objectRepository)
{
this.commandBus = commandBus;
this.objectRepository = objectRepository;
}
public void DoThings()
{
var objects = objectRepository.getAll();
foreach (Thing thing in objects)
{
thing.modifiedOn = DateTime.Now;
objectRepository.Update(thing);
}
}
}
DoingThings
を非同期に処理する方法を知っているプロキシを定義します。
public class DoingThingsAsync : IDoThings
{
private readonly Container container;
public DoingThingsAsync(Container container)
{
this.container = container;
}
public void DoThings()
{
Action handler = () => DoThingsNow();
handler.BeginInvoke(null, null);
}
private void DoThingsNow()
{
// Here we run in a new thread and HERE we ask
// the container for a new DoingThings instance.
// This way we will be sure that all its
// dependencies are safe to use. Never move
// dependencies from thread to thread.
IDoThings doer =
this.container.GetInstance<DoingThings>();
doer.DoThings();
}
}
DoingThings
ここで、 aをに注入する代わりに、 aをコントローラーにSomeController
注入します。DoingThingsAsync
コントローラーは、操作が同期的に実行されるかどうかを認識せず、気にしません。それに加えて、このようにして、ビジネス ロジックをプレゼンテーション ロジックから分離することもできます。これは、多くの正当な理由から重要です。
状態を変更するビジネス オペレーションの基礎としてコマンド パターンを使用することを検討することをお勧めします (まだそのようなものを使用していない場合は、ICommandBus
インターフェイスを考慮してください)。たとえば、この記事を見てください。このパターンを使用すると、特定のコマンドを非同期で実行したり、後で処理するために外部トランザクション キューにバッチ処理したりするように、より簡単に構成できます。これらのコマンドのコンシューマーを変更する必要はありません。