前提
例えば、一つの画面の中でどこかボタンを押すと、バックグラウンドで長い処理が走っていて、その間一定間隔で状態を取得、その情報だけを画面上で更新するというような状況を考えます。
今回は、状態の保存と取得はローカルファイルに書き込むことにしています (C:\test.txt)
非同期している部分は全体で3か所あります。
- 画面上でボタンを押したら、そのformが非同期で通信開始(Ajax)
- かつ、状態を一定間隔で取得しに行く処理も非同期で開始(別のAjax)
- 1で呼ばれたActionResultから非同期の処理がコントローラ側で開始 (delegate)
正直、Ajaxの部分について根本からは理解しておらず、参考サイトから拾ったものをなんとなく動かしながら使っているだけなので、変なとこあればツッコミをいただけると助かります。
環境:
VisualStudio2017 community (win10)
参考:
http://blog.ch3cooh.jp/entry/20140707/1404731608
https://devadjust.exblog.jp/17358343/
実装
まずは通常通りMVCのプロジェクト(記事中では名前をMVCAsyncSample
としました) を作って、
Nugetパッケージの管理→Microsoft.jQuery.Unobtrusive.Ajax をインストール→完了したらリビルド
してください。これやらないと動きません。
次にHomeコントローラを作り、以下のように実装します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.IO;
namespace MVCAsyncSample.Controllers
{
public class HomeController : Controller
{
private string localFolderPath = @"C:\test.txt"; //←実行すると非同期で更新されるファイル
public ActionResult Index()
{
using (StreamWriter sw = new StreamWriter(localFolderPath))
{
sw.Write("まだ処理は完了していません\r\n");
sw.Close();
}
return View();
}
private delegate void HomeControllerDelegate(string buf); // ←非同期実行するための受け皿(delegate)
public ActionResult PartAction()
{
// 非同期で実行
HomeControllerDelegate dlgt = new HomeControllerDelegate(AsyncSample);
dlgt.BeginInvoke("arg", null, dlgt);
return ResultView();
}
private void AsyncSample(string argText) // 引数は最低一つ必要なだけなので値はなんでもよい
{
// 実際にはここに時間のかかる処理が入る。今回はwaitで代用
Thread.Sleep(3000);
// ローカルファイルに書き込む
using (StreamWriter sw = new StreamWriter(localFolderPath))
{
sw.Write("処理が完了しました");
sw.Close();
}
}
public ActionResult ResultView()
{
// ローカルファイルを見に行く
using (StreamReader sr = new StreamReader(localFolderPath))
{
TempData["message"] = sr.ReadLine();
sr.Close();
}
return PartialView("ResultView");
}
// ↓は今回使ってないけどこいうやり方もあるので勉強して別途記事を書きたい ----
[HttpGet]
public async Task<ActionResult> ExecProcess()
{
var heavyTask1 = Task.Run(async () =>
{
await Task.Delay(10000);
return 10;
});
var heavyTask2 = Task.Run(async () =>
{
await Task.Delay(5000);
return 20;
});
await Task.WhenAll(heavyTask1, heavyTask2);
ViewBag.val1 = heavyTask1.Result;
ViewBag.val2 = heavyTask2.Result;
//return View(heavyTask1.Result + heavyTask2.Result);
return View();
}
//------------------------------------------------------------------------------
}
}
次に、おそらく既にできあがっているであろうIndex.cshtmlを以下の通りにします。
@{
ViewBag.Title = "Home";
}
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
@using (Ajax.BeginForm("PartAction", "Home", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "result" }))
{
<div class="form-group">
<div class="col-xs-2">
<input class="btn btn-default" type="submit" style="margin-top:8px" value="非同期処理を実行" id="runbutton" />
</div>
</div>
}
<br /><br /><br />
<div id="result">実行結果をここに更新</div>
<script>
document.getElementById("runbutton").onclick = function (){
$.ajaxSetup({ cache: false });
setInterval(
function () { $("#result").load("/Home/ResultView"); },
5000);
};
</script>
そして、状態を取得する画面として、ResultViewという名前でViewを追加します。
部分ビューなので内容少な目ですが、これでOK。
<h2>ResultView</h2>
<div id="result2">
@TempData["message"]
</div>
delegateって説明だけ読んでてもよくわかんなかったのですが、動かしてみてようやく雰囲気がつかめました。