.NETサービス開発―マルチスレッド使用小結(マルチスレッド使用常識)


しばらくブログを更新していませんでしたが、この半年は『.NETフレームワークデザイン―大手企業クラスフレームワークデザイン芸術』を書いていました.この本が今年の10月にトゥーリン出版社から出版されることを喜んでいます.まず、本書はブロガーの長年の応用フレームワーク学習の総括であり、十数個の重量級フレームワークモデルが含まれていることを明らかにすることができます.これらのモデルは私たちが現在よく使用しているもので、学習フレームワークとフレームワーク開発にとって良い参考資料です.期待してください.
さあ、文章のテーマに入ります.
ここ数ヶ月、私はずっとSOAサービス開発の仕事に従事しています.簡単に言えば、サービスインタフェースを提供しています.フロントエンドインタフェースWEBAPIの提供から、バックエンドインタフェースWCFSOAFrameworkの提供まで、多くのマルチスレッドの使用に関する経験を学びました.これらの経験は本人自身の誤った使用後の経験があり、会社の先輩の指摘もあります.つまり、これらのものはあなたがどのように使用するかを意識したことがありません.だから、私と同じように仕事をしている多くの博友たちにまとめる必要があると思います.
サービスの処理手順について説明します.
1.エントリ・スレッドを使用して、長時間呼び出しを処理するには、次の手順に従います.
任意のサービスの呼び出しには、サービスへの最初のエントリメソッドが必要です.この方法は通常分野論理のインタフェースを演じている.(システム用例をサービスインタフェースの区分)このインタフェースを介して用例の呼び出しを行います.長時間のプロセスを処理する必要がある場合、頭が痛いタイムアウト異常に直面します.タイムアウト補償の方法を設計すると複雑で、必要なオーバーヘッドがありません.長時間処理されるサービス呼び出しシーンの多くは、同期データの中で、あるJobWsを介して定期的にデータを同期し(本人がこのプロセスで学んだ)、サービスがどのくらい処理されるか予知できない場合は、基本的に呼び出し側の接続タイムアウト時間を設定します.(そう思うのではないでしょうか?);これは正常で、タイムアウト時間は私たちに使われています.しかし、私たちは私たちの現在のビジネスシーンを無視しています.もしあなたのサービスがステータス値を返さなければ、「実際には独立したスレッドを開いて同期ロジックを処理し、サービスの呼び出し者にできるだけ早く対応を受け取るべきです」.
public class ProductApplicationService
    {
        public void SyncProducts()
        {
            Task.Factory.StartNew(() 
=>
            {
                var productColl = 
DominModel.Products.GetActivateProducts();
                if 
(!productColl.Any()) return; 
                DominModel.Products.WriteProudcts(productColl);
            });
        }
    }

これにより、呼び出し者をできるだけ早く解放することができます.特定の同期ロジックは、個別のスレッドを開くことによって処理される.
サービスがステータス値を返す必要がある場合はどうしますか?実際には、「非同期メッセージアーキテクチャモード」を参照して、メッセージをメッセージキューに書き込むことができます.その後、クライアントが定期的に取りに来たり、プッシュしたりすることができます.現在のサービス方法をスムーズに処理することができ、少なくともシステム全体のパフォーマンスのボトルネックに貢献します.
1.1異常処理:
入口位置には通常、呼び出しの異常情報が記録されます.すなわち、try{}catch{}を追加して、今回呼び出したすべての異常情報をキャプチャします.(もちろん、コードにtry{}catch{}があふれているのはよくないと言うかもしれませんが、見えないところに置いて自動的に処理することができます.これは良いか悪いか、見えないところに配置が欠かせません.カスタム異常タイプの配置が欠かせません.とにかく物事には両面性があります.)
public class ProductApplicationService
{
    public void 
SyncProducts()
    {
        try
        {
            Task.Factory.StartNew(() =>
            {
                var 
productColl = DominModel.Products.GetActivateProducts();
                if 
(!productColl.Any()) return; 
                DominModel.Products.WriteProudcts(productColl);
            });
        }
        catch(Exception exception)
        {
            //    ...
        }
    }
}

このように、大丈夫そうに見えますが、よく見ると、このtry{}catch{}は私たちが開いているスレッドの外にあるので、この方法はとっくに終わっています.開いているスレッドのスタックにはtry{}catch{}メカニズムコードがありません.異常キャプチャをサポートするために同期コードを少し調整する必要があります.
public class ProductApplicationService
{
    public void 
SyncProducts()
    {
        Task.Factory.StartNew(SyncPrdoctsTask);
    } 
    private static void SyncPrdoctsTask()
    {
        try
        {
            var productColl = 
DominModel.Products.GetActivateProducts();
            if 
(!productColl.Any()) return; 
            DominModel.Products.WriteProudcts(productColl);
        }
        catch 
(Exception exception)
        {
            //    ...
        }
    }
}

Resharpのような補助プラグインを装着すると、コードの再構築に役立ち、ある方法を抽出するのが便利で速いです.
上記のコードには、新しく開いたスレッドに異常キャプチャされたコードが含まれている.これにより、プログラムが未処理の異常を多く投げ出すことはなく、重要な論理点でデータが失われる可能性があります.すべての異常はフレームワークによって処理されるべきではありません.私たちは自分で論理点の異常を手動で制御する必要があります.そうすれば、私たちの論理が引き続き実行できることを保証することができます.一部の論理は、異常の発生によって処理プロセス全体を終了することは不可能である.
2.複数組のデータの読み出しを並列に行う
SOAサービスの最外層サービスインタフェースに位置する場合、通常は内部の多くのサービスインタフェースを包装して外部の必要なデータを組み合わせる必要があります.この場合、多くのインタフェースのデータを照会し、データがそろってからフロントエンドに統一的に戻るのを待つ必要があります.私はしばらくフロントエンドH 5にインタフェースを提供していたので、最も感じたのはサービスインタフェースがすべてのデータをフロントエンドに統合する必要があることです.ユーザーの角度から言えば、携帯電話のインタフェースに非同期の現象が現れることを望んでいません.結局、そんなに大きなスクリーンには白いところがあります.しかし、この需要は私たちの開発者に問題をもたらし、順序読み取り方式でデータを組み合わせると、その時間は人には受け入れられないので、複数のバックエンドサービスインタフェースのデータを同時に読み取るために並列を開く必要があります(これらのデータに前後依存関係がないことを前提としています).
public static ProductCollection GetProductByIds(List<long> 
pIds)
{
    var result = new ProductCollection(); 
    Parallel.ForEach(pIds, id =>
    {
        //    
    }); 
    return result;
}

すべてが快適に見え、複数のIDが同じ時間に一緒に運行されていますが、この中に穴があります.
2.1並列スレッド数の制御:
上記のコードでパラレルをオンにすると、GetProductByIdsのビジネスポイントから見るとすべてが順調で、効果は明らかに速くなります.しかし、現在のGetProductByIdsメソッドが処理中に別のサービス呼び出しを開始すると、すべてのリクエストスレッドが占有されているため、サーバ応答が遅くなります.ここでParallelは私たちが考えているほどスマートではなく、状況に応じてスレッド数を制御することができます.パラレル時の最大スレッド数を自分で制御する必要があります.これにより、マルチスレッドが1つのトラフィックポイントに占有されることによるサービスキューの他の後続要求を防止できます(この場合、CPUが必ずしも高くないとは限りません.CPUが高すぎると要求を受け入れられないのは理解できますが、システム設定の問題でスレッド数が足りないことも可能です).
public static ProductCollection GetProductByIds(List<long> 
pIds)
{
    var result = new ProductCollection(); 
    Parallel.ForEach(pIds, new ParallelOptions() { 
MaxDegreeOfParallelism = 5 /*       */}, id =>
    {
        //    
    }); 
    return result;
}

2.2並列処理を用いた場合のデータの前後順が第一原則
この点で私は2回の間違いを犯しました.1回目はフロントエンドに必要なデータの順序を乱して、データの順位が問題になりました.2回目は、データベースに書き込まれた同期データの時間を乱し、プログラムが前回の終了時間に同期を継続できなくなったことです.だから、パラレルを使用するときは、まず現在のデータコンテキストロジックが前後の順序関係を気にしないことを自分に聞いて、パラレルを開くとすべてのデータは必要ありません.
3.パラレルライブラリで起動したスレッドの代わりにスレッドを手動で開く
現在、私たちが提供しているサービスインタフェースは、非同期asyncをどれだけ少なく使用していますか.おそらく、私たちのシステムにポイント同時量を言及させ、貴重なリクエスト処理スレッドを待つのではなく、タイムリーにシステムに再利用させることができるようにしたいと思っています.
多分コードはこうなります.サービス入口:
public async Task<int> OperationProduct(long ids)
{
    return await DominModel.Products.OperationProduct(ids);
}

ビジネスロジック:
public static async Task<int> OperationProduct(long 
ids)
{
    return await Task.Factory.StartNew<int>(() =>
      {
          System.Threading.Thread.Sleep(5000);
          return 100; 
          //                       ,                。
      });
}

実際には、最後に新しいスレッドを開いたとき、この新しいスレッドはawitのスレッドと同じタイプであり、同時性を向上させることはなく、頻繁な切り替えスレッドによってパフォーマンスに影響を与えます.本当にasyncを実用的にするには、手動で新しいスレッドを開いて同時性を向上させます.(前提は、現在のシステム全体のCPUとスレッドの割合を理解していることです.つまり、2つの手動スレッドを開くのは問題ありませんが、同時の入り口に置くには慎重に考えてください)
Taskで手動スレッドを開くのはちょっと面倒ですが、コードを見てください.
public async Task<int> OperationProduct(long 
id)
{
    var funResult = new AWaitTaskResultValues<int>();
    return await DominModel.Products.OperationProduct(id, funResult);
} 
public static Task<int> OperationProduct(long id, 
AWaitTaskResultValues<int> result)
        {
            var 
taskMock = new Task<int>(() => { return 0; 
});//    await    ,          “      ” 
            var thread = new Thread((threadIds) 
=>
            {
                Thread.Sleep(7000); 
                result.ResultValue = 100; 
                taskMock.Start();//         ,         。
            }); 
            thread.Start(); 
            return taskMock;
        }

このように面倒なのは、スレッドをブロックするのではなくawaitスレッドを解放するためです.私は簡単なテストで少量のスレッドを使用して、より多くの同時要求を処理することができます.
作者:王清培
出典:http://wangqingpei557.blog.51cto.com/
本文の著作権は著者と51 CTOの共有に帰属し、転載を歓迎するが、著者の同意を得ずにこの声明を保留し、文章のページの明らかな位置で原文の接続を与えなければならない.そうしないと、法律責任を追及する権利を保留する.