web-dev-qa-db-ja.com

スレッドを使用して、IISで長時間実行されるジョブを実行できますか?

ASP.Netアプリケーションでは、ユーザーがWebページのボタンをクリックすると、イベントハンドラーを介してサーバー上のオブジェクトがインスタンス化され、オブジェクトのメソッドが呼び出されます。このメソッドは外部システムに移動して処理を行いますが、これには時間がかかる場合があります。そのため、別のスレッドでそのメソッド呼び出しを実行して、「要求が送信されました」というメッセージをユーザーに返すことができます。ユーザーがオブジェクトのステータスをポーリングし続けることができればさらに良いと思いますが、私はこれをファイアアンドフォーゲットとして行うことを合理的に満足しています。

ユーザーセッションが期限切れになっても、IISがスレッドの実行を許可するかどうかはわかりません。ユーザーがイベントを発生させ、サーバー上でオブジェクトをインスタンス化し、新しいスレッドでメソッドを発生させることを想像してください。ユーザーは「リクエストが送信されました」というメッセージに満足し、ブラウザを閉じます。最終的に、このユーザーセッションはIISでタイムアウトになりますが、スレッドはまだ実行中で、作業を行っている可能性があります。 IISは、スレッドが実行し続けることを許可しますか、それともユーザーセッションが期限切れになるとスレッドを強制終了してオブジェクトを破棄しますか?

編集:回答とコメントから、これを行う最善の方法は、長時間実行される処理をIISの外部に移動することであると理解しています。他のすべてとは別に、これはappdomainリサイクルの問題を扱います。実際には、限られた時間でバージョン1をリリースする必要があり、既存のフレームワーク内で作業する必要があるため、サービスレイヤーを回避したいので、IIS内のスレッドを起動するだけです。実際には、ここでの「長時間実行」はほんの数分であり、Webサイトでの同時実行性は低いため問題ありません。しかし、次のバージョンでは間違いなく別のサービスレイヤーに分割する必要があります。

81
Frans

あなたが望むものを達成することができますが、それは一般的に悪い考えです。いくつかのASP.NETブログおよびCMSエンジンは、共有ホスティングシステムにインストール可能であり、インストールする必要のあるWindowsサービスに依存しないため、このアプローチを採用しています。通常、アプリの起動時にGlobal.asaxで長時間実行されるスレッドを開始し、そのスレッドプロセスでタスクをキューに入れます。

IIS/ASP.NETがリクエストを処理するために使用できるリソースを削減することに加えて、AppDomainがリサイクルされるときにスレッドが強制終了されるという問題もあり、実行中のタスクの永続性に対処する必要があります。 AppDomainが復旧したときに作業を開始することもできます。

多くの場合、AppDomainはデフォルトの間隔で自動的にリサイクルされ、web.configなどを更新する場合もあることに注意してください。

いつでも強制終了されたスレッドの永続性とトランザクションの側面を処理できる場合は、一定の間隔でサイトにリクエストを行う外部プロセスを使用することで、AppDomainのリサイクルを回避できます。 X分以内に自動的に再起動することが保証されています。

繰り返しますが、これは一般的に悪い考えです。

編集:このテクニックの実際の例をいくつか示します:

コミュニティサーバー:Windowsサービスとバックグラウンドスレッドを使用して、スケジュールされた間隔でコードを実行するWebサイトの初回起動時にバックグラウンドスレッドを作成する

編集(遠い未来から)-最近では Hangfire を使用します。

63

私は受け入れられた答えに同意しません。

バックグラウンドスレッド(またはTask.Factory.StartNewで始まるタスク)の使用は、ASP.NETでは問題ありません。すべてのホスティング環境と同様に、シャットダウンを管理する機能を理解し、協力したい場合があります。

ASP.NETでは、HostingEnvironment.RegisterObjectメソッドを使用して、シャットダウン時に正常に停止する必要がある作業を登録できます。 この記事 およびディスカッションのコメントを参照してください。

(Gerardがコメントで指摘しているように、HostingEnvironment.QueueBackgroundWorkItemRegisterObjectを呼び出して、バックグラウンドアイテムが動作するスケジューラーを登録するようになりました。全体的に新しいメソッドはタスクベースなので、より優れています。 )

よくないアイデアだとよく耳にする一般的なテーマについては、Windowsサービス(または別の種類のプロセス外アプリケーション)を展開する代替案を検討してください。

  • Webデプロイで簡単なデプロイメントが不要
  • Azure Webサイトだけに展開することはできません
  • バックグラウンドタスクの性質に応じて、プロセスは通信する必要があります。つまり、何らかの形式のIPCか、サービスが共通データベースにアクセスする必要があります。

また、一部の高度なシナリオでは、リクエストと同じアドレス空間でバックグラウンドスレッドを実行する必要がある場合もあります。 ASP.NETがこれを行うことができるという事実は、.NETによって可能になった大きな利点であると思います。

34
John

このタスクにIISスレッドプールからのスレッドを使用したくないのは、そのスレッドが今後のリクエストを処理できなくなるためです。 ASP.NET 2.0の非同期ページ 、しかし、それは実際には正しい答えではありません。代わりに、あなたが利益を得ると思われるのは Microsoft Message Queuing 。基本的には、タスクの詳細をキューに追加し、別のバックグラウンドプロセス(おそらくWindowsサービス)がそのタスクの実行を担当します。行は、バックグラウンドプロセスがIISから完全に分離されていることです。

7
senfo

このような要件には HangFire を使用することをお勧めします。ナイスファイヤーアンドフォージエンジンはバックグラウンドで実行され、異なるアーキテクチャをサポートします。永続ストレージによってバックアップされているため、信頼性があります。

6
Vipul

ここに良いスレッドとサンプルコードがあります: http://forums.asp.net/t/1534903.aspx?PageIndex=2

アプリプールを維持するために、Webサイトのキープアライブページをスレッドから呼び出すというアイデアを思いつきました。この方法を使用している場合、アプリケーションはいつでもリサイクルできるため、本当に優れた回復処理が必要であることを覚えておいてください。多くの人が述べているように、他のサービスオプションにアクセスできる場合、これは正しいアプローチではありませんが、共有ホスティングの場合、これが唯一のオプションの1つである可能性があります。

アプリプールを有効に保つために、スレッドの処理中に自分のサイトにリクエストを行うことができます。これにより、プロセスが長時間実行された場合にアプリプールを維持できます。

string tempStr = GetUrlPageSource("http://www.mysite.com/keepalive.aspx");


    public static string GetUrlPageSource(string url)
    {
        string returnString = "";

        try
        {
            Uri uri = new Uri(url);
            if (uri.Scheme == Uri.UriSchemeHttp)
            {
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
                CookieContainer cookieJar = new CookieContainer();

                req.CookieContainer = cookieJar;

                //set the request timeout to 60 seconds
                req.Timeout = 60000;
                req.UserAgent = "MyAgent";

                //we do not want to request a persistent connection
                req.KeepAlive = false;

                HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
                Stream stream = resp.GetResponseStream();
                StreamReader sr = new StreamReader(stream);

                returnString = sr.ReadToEnd();

                sr.Close();
                stream.Close();
                resp.Close();
            }
        }
        catch
        {
            returnString = "";
        }

        return returnString;
    }
5
Daniel

私たちはこのパスを開始しましたが、アプリが1つのサーバー上にあるときは実際にうまくいきました。複数のマシンにスケールアウトする(またはWebガレンで複数のw3wpを使用する)場合は、ワークキュー、エラー処理、再試行、および1つだけを確実にロックするトリッキーな問題の管理方法を再評価し、検討する必要がありました。サーバーは次のアイテムをピックアップします。

...バックグラウンド処理エンジンを作成するビジネスではないことに気付いたため、既存のソリューションを探し、素晴らしいOSSプロジェクトを使用して着陸しました hangfire

Sergey Odinokovが作成した本物のgemは非常に使いやすく、作業の永続化とキューイングのバックエンドを交換できます。 Hangfireはバックグラウンドスレッドを使用しますが、ジョブを保持し、再試行を処理し、作業キューを可視化します。したがって、hangfireのジョブは堅牢であり、リサイクルされるappdomainsのすべての気まぐれを生き延びます。

その基本的なセットアップでは、ストレージとしてsqlサーバーを使用しますが、スケールアップする時間に応じてRedisまたはMSMQに交換できます。また、すべてのジョブとそのステータスを視覚化するための優れたUIを備えており、ジョブを再キューイングできます。

私のポイントは、バックグラウンドスレッドで必要なことを完全に行うことはできますが、スケーラブルで堅牢にするために多くの作業があることです。単純なワークロードには適していますが、事態がより複雑になる場合は、この作業を行うよりも専用のライブラリを使用することを好みます。

使用可能なオプションの詳細については、Scott Hanselmanの blog をご覧ください。asp.netでバックグラウンドジョブを処理するためのいくつかのオプションについて説明しています。 (彼はhangfireに熱烈なレビューを与えました)

また、Johnが参照したように、Phil Haackの blog でアプローチが問題となる理由と、appdomainがアンロードされたときにスレッドでの作業を適切に停止する方法を読む価値があります。

4
Avner

バックグラウンドでタスクを実行でき、リクエストが終了した後でもタスクは完了します。 キャッチされない例外をスローさせないでください。通常、常に例外をスローする必要があります。新しいスレッドで例外がスローされると、リクエストのコンテキスト内にいないため、 IISワーカープロセス-w3wp.exe がクラッシュします。また、使用中の場合は、インプロセスメモリでバックアップされたセッションに加えて、実行中の他のバックグラウンドタスクを強制終了します。これは診断が困難であるため、この方法は推奨されません。

2

そのタスクを実行するWindowsサービスを作成できますか?次に、Webサーバーから.NETリモート処理を使用してWindowsサービスを呼び出し、アクションを実行しますか?もしそうなら、それは私がすることです。

これにより、IISで中継する必要がなくなり、処理能力の一部が制限されます。

そうでない場合は、プロセスが完了するまでユーザーをそこに座らせます。そうすることで、IISによって強制終了されずに完了します。

2
David Basarab

IISで長時間実行される作業をホストする1つのサポートされた方法があるようです。 Workflow Services は、特に Windows Server AppFabric と合わせて設計されているようです。この設計では、長期にわたる作業の自動永続化と再開をサポートすることにより、アプリケーションプールのリサイクルが可能になります。

2
Olly

非同期タスクを実行する代理プロセスを作成するだけです。 Windowsサービスである必要はありません(ただし、ほとんどの場合、これがより最適な方法です。MSMQは強制終了です。

1
Matt Davison