Pav*_*ula 1 asp.net task-parallel-library asp.net-mvc-3
基本上我想实现简单的搜索功能,每当用户在视图的文本框中输入一些关键字并单击提交按钮时,我想使用TPL异步机制对ASYNC调用预定义的网站URL.当我使用控制台应用程序执行相同操作时,它的工作方式就像魅力,但不适用于ASP.NET MVC3.
我找不到原因
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
return View();
}
[HttpPost]
public ActionResult Index(string text)
{
string[] url = { "http://www.msnbc.com", "http://www.yahoo.com",
"http://www.nytimes.com", "http://www.washingtonpost.com",
"http://www.latimes.com", "http://www.newsday.com" };
Task<string[]> webTask = this.GetWordCounts(url, text);
string[] results = null;
try
{
results = webTask.Result;
}
catch (AggregateException e)
{
}
return View("Index", results);
}
//Taken from MSDN
Task<string[]> GetWordCounts(string[] urls, string name)
{
TaskCompletionSource<string[]> tcs = new TaskCompletionSource<string[]>();
WebClient[] webClients = new WebClient[urls.Length];
object m_lock = new object();
int count = 0;
List<string> results = new List<string>();
for (int i = 0; i < urls.Length; i++)
{
webClients[i] = new WebClient();
#region callback
// Specify the callback for the DownloadStringCompleted
// event that will be raised by this WebClient instance.
webClients[i].DownloadStringCompleted += (obj, args) =>
{
if (args.Cancelled == true)
{
tcs.TrySetCanceled();
return;
}
else if (args.Error != null)
{
// Pass through to the underlying Task
// any exceptions thrown by the WebClient
// during the asynchronous operation.
tcs.TrySetException(args.Error);
return;
}
else
{
// Split the string into an array of words,
// then count the number of elements that match
// the search term.
string[] words = null;
words = args.Result.Split(' ');
string NAME = name.ToUpper();
int nameCount = (from word in words.AsParallel()
where word.ToUpper().Contains(NAME)
select word)
.Count();
// Associate the results with the url, and add new string to the array that
// the underlying Task object will return in its Result property.
results.Add(String.Format("{0} has {1} instances of {2}", args.UserState, nameCount, name));
}
// If this is the last async operation to complete,
// then set the Result property on the underlying Task.
lock (m_lock)
{
count++;
if (count == urls.Length)
{
tcs.TrySetResult(results.ToArray());
}
}
};
#endregion
// Call DownloadStringAsync for each URL.
Uri address = null;
try
{
address = new Uri(urls[i]);
// Pass the address, and also use it for the userToken
// to identify the page when the delegate is invoked.
webClients[i].DownloadStringAsync(address, address);
}
catch (UriFormatException ex)
{
// Abandon the entire operation if one url is malformed.
// Other actions are possible here.
tcs.TrySetException(ex);
return tcs.Task;
}
}
// Return the underlying Task. The client code
// waits on the Result property, and handles exceptions
// in the try-catch block there.
return tcs.Task;
}
Run Code Online (Sandbox Code Playgroud)
这是我的观点 - 现在我有一个硬编码关键字作为微软
@using (Html.BeginForm("Index", "Home", new { text = "Microsoft" }))
{
<input type="submit" />
}
Run Code Online (Sandbox Code Playgroud)
更新:它永远保留在Index Post方法的try块中
我建议您使用AsyncController执行此任务,以避免危害ASP.NET工作线程,这是ASP.NET应用程序可能发生的最糟糕的事情=>耗尽工作线程.这就像在沙漠中间耗尽燃料一样.你肯定死了.
因此,让我们首先编写一个扩展方法,允许我们将基于WebClient事件的旧模式转换为基于任务的新模式:
public static class TaskExtensions
{
public static Task<string> DownloadStringAsTask(this string url)
{
var tcs = new TaskCompletionSource<string>(url);
var client = new WebClient();
client.DownloadStringCompleted += (sender, args) =>
{
if (args.Error != null)
{
tcs.SetException(args.Error);
}
else
{
tcs.SetResult(args.Result);
}
};
client.DownloadStringAsync(new Uri(url));
return tcs.Task;
}
}
Run Code Online (Sandbox Code Playgroud)
有了这个扩展方法,我们现在可以定义一个基本上反映我们视图要求的视图模型:
public class DownloadResultViewModel
{
public string Url { get; set; }
public int WordCount { get; set; }
public string Error { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
然后我们转到一个包含2个动作的异步控制器:一个标准的同步Index动作,它将呈现搜索表单,一个异步Search动作将执行实际的工作:
public class HomeController : AsyncController
{
public ActionResult Index()
{
return View();
}
[AsyncTimeout(600000)]
[HttpPost]
public void SearchAsync(string searchText)
{
AsyncManager.Parameters["searchText"] = searchText;
string[] urls =
{
"http://www.msnbc.com",
"http://www.yahoo.com",
"http://www.nytimes.com",
"http://www.washingtonpost.com",
"http://www.latimes.com",
"http://www.unexistentdomainthatwillcrash.com",
"http://www.newsday.com"
};
var tasks = urls.Select(url => url.DownloadStringAsTask());
AsyncManager.OutstandingOperations.Increment(urls.Length);
Task.Factory.ContinueWhenAll(tasks.ToArray(), allTasks =>
{
var results =
from task in allTasks
let error = task.IsFaulted ? task.Exception.Message : null
let result = !task.IsFaulted ? task.Result : string.Empty
select new DownloadResultViewModel
{
Url = (string)task.AsyncState,
Error = error,
WordCount = result.Split(' ')
.Where(x => string.Equals(x, searchText, StringComparison.OrdinalIgnoreCase))
.Count()
};
AsyncManager.Parameters["results"] = results;
AsyncManager.OutstandingOperations.Decrement(urls.Length);
});
}
public ActionResult SearchCompleted(IEnumerable<DownloadResultViewModel> results)
{
return View("index", results);
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们定义一个~/Views/Home/Index.cshtml包含搜索逻辑和结果的视图:
@model IEnumerable<DownloadResultViewModel>
@using (Html.BeginForm("search", null, new { searchText = "politics" }))
{
<button type="submit">Search</button>
}
@if (Model != null)
{
<h3>Search results</h3>
<table>
<thead>
<tr>
<th>Url</th>
<th>Word count</th>
</tr>
</thead>
<tbody>
@Html.DisplayForModel()
</tbody>
</table>
}
Run Code Online (Sandbox Code Playgroud)
当然还有为模型(~/Views/Shared/DisplayTemplates/DownloadResultViewModel.cshtml)的每个元素自动渲染的相应显示模板:
@model DownloadResultViewModel
<tr>
<td>@Html.DisplayFor(x => x.Url)</td>
<td>
@if (Model.Error != null)
{
@Html.DisplayFor(x => x.Error)
}
else
{
@Html.DisplayFor(x => x.WordCount)
}
</td>
</tr>
Run Code Online (Sandbox Code Playgroud)
现在,由于搜索操作可能需要相当长的时间,您的用户可能会很快感到无聊,而无法使用您的网页提供的其他百分之一的功能.
在这种情况下,Search使用AJAX请求调用控制器操作并显示一个微调器来通知用户他们的搜索正在进行但没有冻结网页允许他们做其他事情(显然没有离开页面)是绝对微不足道的..
那么,让我们这样做,好吗?
我们首先将结果外部化为partial(~/Views/Home/_Results.cshtml)而不触及显示模板:
@model IEnumerable<DownloadResultViewModel>
@if (Model != null)
{
<h3>Search results</h3>
<table>
<thead>
<tr>
<th>Url</th>
<th>Word count</th>
</tr>
</thead>
<tbody>
@Html.DisplayForModel()
</tbody>
</table>
}
Run Code Online (Sandbox Code Playgroud)
我们调整我们的~/Views/Home/Index.cshtml观点来使用这个部分:
@model IEnumerable<DownloadResultViewModel>
@using (Html.BeginForm("search", null, new { searchText = "politics" }))
{
<button type="submit">Search</button>
}
<div id="results">
@Html.Partial("_Results")
</div>
Run Code Online (Sandbox Code Playgroud)
当然,SearchCompleted控制器动作现在必须只返回部分结果:
public ActionResult SearchCompleted(IEnumerable<DownloadResultViewModel> results)
{
return PartialView("_Results", results);
}
Run Code Online (Sandbox Code Playgroud)
现在剩下的就是编写一个简单的JavaScript来AJAX化我们的搜索表单.所以这可能会发生在我们的布局中引用的单独的js中:
$(function () {
$('form').submit(function () {
$.ajax({
url: this.action,
type: this.method,
success: function (results) {
$('#results').html(results);
}
});
return false;
});
});
Run Code Online (Sandbox Code Playgroud)
根据您是在<head>部分中还是在主体末尾引用此脚本,您可能不需要将其包装在document.ready.如果脚本在最后,您可以从我的示例中删除包装document.ready函数.
最后一部分是向用户提供一些视觉指示,表明该网站实际上正在执行搜索.这可以使用我们可能订阅的全局ajax事件处理程序来完成:
$(function () {
$(document).ajaxStart(function () {
$('#results').html('searching ...');
});
});
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1721 次 |
| 最近记录: |