Windows Azure の WebRole で効率的な Timer 処理をする


こんにちは。

昨日は、Web アプリケーションのパフォーマンス改善 (スレッドの有効活用とテスト方法) のハンズオン・ラボにご参加いただき、ありがとうございました。そして、また、失敗してしまいました。申し訳ありません !

すご~く気になったので、今朝調査しました。(気になって、夜も眠れませんでした . . .)

まず、ハンズオン参加者の皆様が昨日アクセスした「重いサービス」は、多くの負荷に耐えられるよう、ASP.NET MVC 2 の手法を使って、下記の通り非同期で記述していました。(というか、前日 気になって、以下の通り書き換えました。そして、すみません、これが仇となりました。)

. . .
using System.Threading;
. . .

public class HomeController : AsyncController
{
  [AsyncTimeout(45000)]
  public void IndexAsync()
  {
    AsyncManager.OutstandingOperations.Increment();
    Timer timer = new Timer(new TimerCallback((s) =>
    {
      AsyncManager.OutstandingOperations.Decrement();
    }), null, 30000, Timeout.Infinite);
  }

  public ActionResult IndexCompleted()
  {
    return View();
  }
}
. . .

30 秒待って結果を返すだけの意味のない Timer 処理 (ASP.NET MVC の Action) ですが、これが、Windows Azure Cloud Service の WebRole にホストすると問題を引き起こします。Visual Studio Load Test でテストをすると一目瞭然ですが、頻繁に Async の Timeout エラーが発生します。

Windows Server にホストすると、このようなエラーは発生しませんから、Windows Azure 固有のリソース管理に起因すると思われますが、すみません、明確な理由はよくわかりません。ただ、いろいろ調べてみると、Windows Azure 上における AsyncController と Timer の相性に起因している感じです。(時間がかかっているのではなく、そもそも、Async の Completion メソッドが呼ばれません。)
Windows Azure 上では、どうも、昨日紹介した Task による方法を使ったほうが良さそうです。

そこで、昨日 解説したように、Worker Thread の解放をおこなう方法 (Worker Thread をブロックしない方法) で、Task を使って書き換えます。
IO 処理なら、昨日紹介したように Completion Port による待機が発生するので問題ありませんが、今回は、単に待機 (Wait) する処理なので注意が必要です。例えば、下記のコードは悪い例です。(真似しないでください。これなら、同期で書いても同じです。) Completion Port などで待機をおこなう処理ではないため、結局、Thread は実行中にブロックされてしまいます。

. . .
using System.Threading.Tasks;
. . .

public class HomeController : Controller
{
  public Task<ActionResult> Index()
  {
    Task<ActionResult> t =
      Task.Factory.StartNew<ActionResult>(() =>
      {
        System.Threading.Thread.Sleep(30000);
        return View();
      });
    return t;
  }
}
. . .

Task を使って待機する場合は、下記の通り、Task が持っている待機メソッド (今回の場合、Delay) を使って記述します。この方法だと、Worker Thread のブロックはおこなわず、効率的に 30 秒の待機をおこないます。

. . .
using System.Threading.Tasks;
. . .

public class HomeController : Controller
{
  public Task<ActionResult> Index()
  {
    Task<ActionResult> t =
      Task.Delay(30000).ContinueWith<ActionResult>((t1) =>
      {
        return View();
      });
    return t;
  }
}
. . .

昨日のハンズオン・ラボで使ったサイトは修正しましたので、ハンズオン参加者の方は、再度 お試しください。(お手数おかけし、すみません。)

なお、リソース増強に頼りたくないので、依然、Small Size の 1 インスタンス (1 コア) の強気で行かせて頂きます !

ある有名な作家がテレビで、「自分は作家だから、『言葉にできない』 とは表現したくない」と言ってましたが、プログラマーの私としても、「プログラミングこそが、問題を本質的に克服する」と信じつつ。。。
(実際、Cloud Service の Small インスタンスでも Available の Worker Thread が 32767 もあるので、今回のハンズオンは同期処理でも充分なんですけどね。私が納得できません。。。)

 

Comments (0)

Skip to main content