3

私はいくつかのコード ブロックを非同期的に処理したい MVC3 アプリケーションに取り組んでいます。これらのコード ブロックは、いくつかの db 操作を実行する必要があり、依存性注入のセットアップを通じて開始される DAL リポジトリにアクセスする必要があります。リポジトリの有効期間を「http リクエストごとのインスタンス」の有効期間に設定しました。非同期操作の有効期間を延長する方法を探しています。

public class AsyncController : Controller
{
    private readonly ICommandBus commandBus;
    private readonly IObjectRepository objectRepository;

    public ActionResult DoThings()
    {
        DoThingsAsync();
        return View();
    }

    private delegate void DoThingsDelegate();
    private void DoThingsAsync()
    {
        var handler = new DoThingsDelegate(DoThingsNow);
        handler.BeginInvoke(null, null);
    }

    private void DoThingsNow()
    {
        var objects = objectRepository.getAll();
        foreach (Thing thing in objects)
        {
            thing.modifiedOn = DateTime.Now;
            objectRepository.Update(thing);
        }
    }
}  

objectRepository は、リクエストの存続期間中開始されます。このオブジェクトのガベージ コレクションをスキップしたいのは、1 つのコントローラーの 1 つのメソッドのみです。メソッドとデリゲートにパラメーターとしてリポジトリを渡そうとしましたが、その有効期間は延長されませんでした。

4

1 に答える 1

4

新しいスレッドを開始するときは、コンテナにまったく新しいオブジェクト グラフを作成させるのが賢明です。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インターフェイスを考慮してください)。たとえば、この記事を見てください。このパターンを使用すると、特定のコマンドを非同期で実行したり、後で処理するために外部トランザクション キューにバッチ処理したりするように、より簡単に構成できます。これらのコマンドのコンシューマーを変更する必要はありません。

于 2011-12-22T14:04:58.307 に答える