1

次の状況を考えてみましょう: 注文がバーコード スキャナーでスキャンされ、注文番号が Windows サービス アプリケーションに送信され、価格が計算されて返送されます。とりわけ割引クーポンが考慮され、割引は別のアセンブリで処理されます。私がやりたかったのは、実行時にアセンブリをアンロードできるようにして、サービスを停止することなく dll を置き換えることができるようにすることです。(サービスの起動には 30 分かかります) そこで、アセンブリを読み込んでそこでコードを実行する新しい AppDomain を作成するというアイデアを思いつきました。通信は、名前付きパイプとシリアル化によって行われます。機能的には問題なく動作しますが、本番環境ではパフォーマンスが非常に低下します。以下のコードをできるだけ速く実行する方法について、誰かアドバイスはありますか?

コードの説明: 割引のある注文ごとに DoAction が呼び出されます。まず、名前付きパイプ クライアントとして機能するスレッドを起動します。そのスレッドは、クライアントに返送される価格を受け取ります。次に、新しい AppDomain がまだロードされていない場合はロードされ、その AppDomain のコンテキストで AppDomainCallback が実行されます。そこで名前付きパイプ サーバーが開始され、ディスカウント コードを含むアセンブリが読み込まれ、クライアントの接続時に呼び出され、結果が逆シリアル化されてクライアント スレッドに戻され、DoAction から返されます。そのため、多くのスレッドの待機とシリアル化が進行中ですが、それを高速化する方法がわかりません。

[Serializable]
internal class ActionLoader
{
    private const string DOMAINNAME = "Actions";

    private const string PIPE_TO = "PipeTo";
    private const string PIPE_BACK = "PipeBack";

    private string assemblyName;
    private string className;
    private string methodName;
    private List<object> parameters;

    private static BinaryFormatter formatter = new BinaryFormatter();

    public ActionLoader(string assemblyName, string className, string methodName, List<object> parameters)
    {
        this.assemblyName = assemblyName;
        this.className = className;
        this.methodName = methodName;
        this.parameters = parameters;
    }

    private static AppDomain domain = AppDomain.CreateDomain(DOMAINNAME);

    public OrderPrice DoAction()
    {
        // after clientThread is done it fills RetVal
        ThreadedExecuter<OrderPrice> clientThread = new ThreadedExecuter<OrderPrice>(NamedPipeClient, parameters);
        clientThread.Start();

        if (domain == null) // domain can be unloaded by ropsrefresh so check if it should be created again
        {
            domain = AppDomain.CreateDomain(DOMAINNAME);
        }
        // AppDomainCallback runs in the context of appdomain so dll's get loaded in there and not in CurrentDomain
        domain.DoCallBack(AppDomainCallback);

        clientThread.Thread.Join();

        return clientThread.RetVal; // return price deseralized from AppDomain
    }

    public static void UnloadAppDomain() // called by ropsrefresh => refresh config
    {
        if (domain != null)
        {
            AppDomain.Unload(domain);
            domain = null;
        }
    }

    private void AppDomainCallback()
    {
        OrderPrice price = null;

        Assembly assembly = Assembly.LoadFrom(assemblyName);

        object action = assembly.CreateInstance(className);
        MethodInfo mi = action.GetType().GetMethod(methodName);

        // using pipes to communicate between AppDomains
        using (NamedPipeServerStream stream = new NamedPipeServerStream(PIPE_TO))
        {
            stream.WaitForConnection();

            List<object> parameters = (List<object>)DeserializeFromStream(stream);

            Type t = action.GetType();

            if (mi != null)
                price = (OrderPrice)mi.Invoke(action, parameters.ToArray());
        }

        // server becomes client to serialize data back
        using (NamedPipeClientStream stream = new NamedPipeClientStream(PIPE_BACK))
        {
            stream.Connect();
            SerializeToStream(stream, price);
        }
    }

    private static OrderPrice NamedPipeClient(object parameters)
    {
        OrderPrice price = null;

        // using pipes to communicate between AppDomains
        using (NamedPipeClientStream stream = new NamedPipeClientStream(PIPE_TO))
        {
            stream.Connect();
            SerializeToStream(stream, parameters); // serialize function parameters to pipe stream
        }

        using (NamedPipeServerStream stream = new NamedPipeServerStream(PIPE_BACK))
        {
            stream.WaitForConnection();

            price = (OrderPrice)DeserializeFromStream(stream);
        }

        return price; // returns deserialized price to ThreadedExecutor
    }

    private static object DeserializeFromStream(Stream stream)
    {
        return formatter.Deserialize(stream);
    }

    private static void SerializeToStream(Stream stream, object parameters)
    {            
        formatter.Serialize(stream, parameters);
    }
}
4

1 に答える 1

1

Task ライブラリを使用するようにコードを書き直しました。これは、Windows の ThreadPool を使用するため、パフォーマンスが向上します。task.Result はタスクが完了するまでブロックされるため、参加はもう必要ありません。

public OrderPrice DoAction()
{
    Task<OrderPrice> task = Task<OrderPrice>.Factory.StartNew(NamedPipeClient);

    if (domain == null)
        domain = AppDomain.CreateDomain(DOMAINNAME);
    domain.DoCallBack(AppDomainCallback);

    return task.Result; 
}
于 2013-08-22T06:49:28.663 に答える