Inspired by How to: Implement an Asynchronous Service Operation and Building Task Based WCF Services with Task Parallel Library, I'm trying to make a WCF web service with an operation that is executed asynchronously.
The idea is that I have a method that does work that lasts anywhere from a second to a minute that is called by a button on a web page and I have a timer that calls another method in the same service that eventually will return the asynchronous operation's status (working or not).
So I set up a dummy example and my asynchronous operation actually blocks my Web Serivce.
[ServiceContract(Namespace = "")]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class Service1
{
[OperationContract(AsyncPattern = true, Action = "TestServiceMethod", Name = "TestServiceMethod", ReplyAction = "TestServiceMethodReply")]
public IAsyncResult BeginTestServiceMethod(string request, AsyncCallback callback, object asyncState)
{
var task = new Task<string>((state) =>
{
SpinWait.SpinUntil(() => { return false; }, 5000);
return request;
}, asyncState);
task.ContinueWith((t) => { callback(t); });
task.Start();
return task;
}
public string EndTestServiceMethod(IAsyncResult result)
{
var task = (Task<string>)result;
return task.Result;
}
[OperationContract]
public string OtherTest()
{
return "OtherTest";
}
}
and this is the javascript on my page (the click function is activated by clicking a button)
function Click() {
var service = new Service1();
service.TestServiceMethod("Dummy", PopWord);
service.OtherTest(PopWord);
}
function PopWord(word) {
alert(word);
}
The result is a 5 seconds wait when I click on the button, followed by "Dummy" and "OtherTest" popping one after the other. Expected behavior would be "OtherTest" popping with "Dummy" 5 seconds later.
Can anyone spot what I am doing wrong or perhaps suggest another approach?
I'm guessing you are running on cassini (development server) and not on IIS?
If so, I've seen quite a few people saying that cassini cannot execute requests in parallel.
I can't seem to locate any documentation on this from microsoft but there are quite a few posts on stack overflow similar to the following.
ASP.NET Development Server concurrent processing doesn't work
ASP.NET Dev Server (Cassini), IIS Express and multiple threads
Related
I have an OWIN-based ASP.NET Web API hosted in a Windows Service. Most of my ApiController actions are async, and accept CancellationToken parameters:
[Route("data/{id}")]
public async Task<IHttpActionResult> GetSomeDataAsync(int id, CancellationToken token)
{
try
{
using (var _dataSource = ...)
{
return Ok(await _dataSource.GetDataAsync(id, token));
}
}
catch (OperationCanceledException ex)
{
return StatusCode(HttpStatusCode.NoContent);
}
}
Using the built-in request-cancellation features of Web API, if the client cancels the request, token is signaled and _dataSource handles it appropriately and throws the OperationCanceledException.
So far, so great.
But when my host process terminates (that is, the Windows Service stops), token isn't signaled and the cancellation-and-bail-out process isn't graceful.
I'm aware of the OWIN environment dictionary's host.onAppDisposing property, and I've dug into the source for the Microsoft.Owin[.*] and Microsoft.AspNet.WebApi.* packages to try and figure out where GetSomeDataAsync's token argument is coming from, but I'm not sure how to connect the pieces together.
I'd like to do something like
class WebServiceInAWindowsService : ServiceBase
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
...
protected override void OnStop()
{
_cts.Cancel();
}
}
But I'm not sure how to get _cts to be the source of the CancellationTokens that get fed to my actions, while not breaking the request-cancellation feature that's working well.
I'm thinking that CancellationTokenSource.CreateLinkedTokenSource() might be useful, but I'm not seeing how to put the pieces together.
Can you help? Thanks!
host.onAppDisposing is triggered when you call Dispose on the value returned from WebApp.Start.
https://github.com/aspnet/AspNetKatana/blob/9f6e09af6bf203744feb5347121fe25f6eec06d8/src/Microsoft.Owin.Hosting/Engine/HostingEngine.cs#L302-L308
https://github.com/aspnet/AspNetKatana/blob/9f6e09af6bf203744feb5347121fe25f6eec06d8/src/Microsoft.Owin.Hosting/Engine/HostingEngine.cs#L112
GetSomeDataAsync's is only associated with the request disconnect token by default (e.g. owin.CallCancelled). Via middleware or otherwise you can replace it with a linked TCS that's also connected to host.onAppDisposing.
Something like:
app.Use(async (env, next) =>
{
var reqAbt = env.Get<CancellationToken>("owin.CallCancelled");
var appAbt = env.Get<CancellationToken>("host.onAppDisposing");
using (linked = CancellationTokenSource.CreateLinkedTokenSource(reqAbt, appAbt))
{
env["owin.CallCancelled"] = linked.Token;
await next();
env["owin.CallCancelled"] = reqAbt;
}
});
We have a three tier infrastructure (front end which is all Web API 2, Middleware which accepts API calls from front end and runs business logic and databases access, then the DB)
I'm trying to find out why our app locks up when I take the middle tier down. We use Memcached for all the reads and the front end serves the cached data just fine, but one of the calls that is made checks to see if the user is logged in. Running on my local machine with one app pool, that call locks the thread (I think) and prevents the rest of the calls from doing anything until the timeout on the autologin call expires.
The code path looks like this:
call to api/autologin --> front end API calls Client.SendAsync (our custom method for passing along data to the middleware), this tries to call the middlewware by using HttpClient.SendAsAsync with a timeout of 3 minutes (Probably should shorten this)
My expectation is that this should release this thread while we are waiting. That does not appear to be the result.
The REALLY weird thing is that when the middleware is down the Client.SendAsync gets ran MANY time, like 10. I thought this was maybe HTTP 2.0 in Chrome, but I switched to Fiddler and it did the same thing. Very weird.
So, two questions.
1. What's with the multiple calls?
2. Why do the threads appear to be getting locked?
Here's the code.
/// <summary>
/// Auto login user if they have the persistent cookies.
/// </summary>
/// <returns>The groups the logged in user has access to in the form of a
LoggedInUserData object.</returns>
[Route("api/cms/autologin/")]
[HttpGet]
public async Task<HttpResponseMessage> AutoLogin()
{
HttpResponseMessage response = await Client.SendAsync(this.Request);
return this.LoginCacheHelper(response);
}
That calls
public static async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
return await Client.SendAsync<string>(request, null, null, false);
}
Which calls
public static async Task<HttpResponseMessage> SendAsync<T>(HttpRequestMessage request, T content = null, string route = null, bool isFile = false, TimeSpan? timeout = null) where T : class
{
// Validate all internal certs.
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
// Determine the route and make sure route has a starting forward slash.
if (!string.IsNullOrWhiteSpace(route) && route.StartsWith("http"))
{
// Check to make sure this is a selinc.com domain for security purposes.
if (Sel.Utils.Validation.UriValidation.IsSelincDomain(route))
{
request.RequestUri = new Uri(route);
}
else
{
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
}
else
{
string middlewareRoute = GetRoute(route, request);
// Change Uri to middle ware.
request.RequestUri = new Uri(Config.MwareSiteUrl + middlewareRoute);
}
// Remove host header
request.Headers.Host = string.Empty;
// Set content of request.
// File content will be kept on the request as is.
if (content != null && !isFile)
{
request.Content = new ObjectContent<T>(content, new JsonMediaTypeFormatter());
}
else if (!isFile)
{
request.Content = null;
}
// Client handler set use cookies to false which will pass along the current cookies
HttpClientHandler clientHandler = new HttpClientHandler() { UseCookies = false };
// The HttpClient object
HttpClient client = new HttpClient(clientHandler);
client.Timeout = timeout ?? new TimeSpan(0, 3, 0);
// Send the request
return await client.SendAsync(request);
}
Adding image of the Network log in Chrome to illustrate the behavior.
Note that if I remove the API call to the autologin, everything works fine. It's the only call in this stack that hits the back end.
Also note: If I modify the SendAsync method to just return a new HttpResponseMessage (and thus do no work) then the autologin basically does nothing, returns quickly and site loads as it should, with the middleware server down. This is just to prove that it is the autologin API call causing the problem. The autologin API call is the only method calling SendAsync at this time so it's a valid test.
// Send the request
////return await client.SendAsync(request);
return new HttpResponseMessage();
Could someone point me to a resource that would help explain how web api (specifically using Owin Self Host) handles request cancellation?
Here's the sequence of events i'm observing:
someone makes a GET from chrome
Api controller (through some layers) fires off an async SQL query
someone hits the X button in chrome (i don't know exactly what happens on socket for this)
What happens next in Web Api??
There was some code running in a controller, does the thread running it get aborted? If it was an async controller awaiting another Task, does that task still have an awaiter in case it returns with an exception?
For context: I do have an async controller awaiting a Task (this is the only call site) which looks to be throwing an unobserved exception in some edge cases. I haven't been able to isolate or re-produce yet :)
I did find something called HttpResponse.ClientDisconnectedToken, but don't know well that is supported in Owin Selfhost + is it even the good thing to use for all user cancels.
I've dealt with this by handing the System.OperationCanceledException in a custom middleware I've registered before WebApi.
public class ExceptionHanldingMiddleware : OwinMiddleware
{
public override async Task Invoke(IOwinContext context)
{
try
{
await Next.Invoke(context);
}
catch (OperationCanceledException) when (context.Request.CallCancelled.IsCancellationRequested)
{
//swallow user-agent cancelling request.
_log.Trace($"client disconnected on request for: {context.Request.Path}.");
}
catch (Exception ex)
{
_log.Error(ex);
context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
context.Response.ReasonPhrase = "Internal Server Error";
}
}
}
As you stated that your async controller is awaiting for a Task, which sometimes got some exception, I suggest you ContinueWith extension method for a task, which can be run only then your task is faulted, like this:
task.ContinueWith(
t =>
logger.Error(t.Exception.Message, t.Exception);
, TaskContinuationOptions.OnlyOnFaulted);
This is a default mechanism to handle the exceptions, and this will work in OWIN application.
Second, as for the cancellation: task can be started with a CancellationToken structure, which can be used for a cancelling the task during the execution. You can read more in the MSDN article.
HttpResponse.ClientDisconnectedToken is used for a situation when the client has been disconnected and the request should not be proceed in execution.
You can use this token, or create your own with CancellationTokenSource, like this:
var source = new CancellationTokenSource();
var token = source.Token;
var task = Task.Factory.StartNew(() =>
{
// Were we already canceled?
ct.ThrowIfCancellationRequested();
var moreToDo = true;
while (moreToDo)
{
// Poll on this property if you have to do
// other cleanup before throwing.
if (ct.IsCancellationRequested)
{
// Clean up here, then...
ct.ThrowIfCancellationRequested();
}
}
}, token);
I am creating an ASP.NET MVC 5 Web site, where I have one operation, which requires a lot of time to be executed(importing e-mails from exchange with EWS2.0 Managed API).
The problem is when a client triggers Import action method, the whole site is blocking and no one can open /Home/Index for example or can't make any request to the server, after while exception is throwed(Timeout) if no one interracts with site during the import process - import is successful otherwise it is not guaranteed because of the timeout exception.
How can I manage to start Importing and then redirect users to /home/index and continue importing on server side..?
Here is what I've tried:
public ActionResult Exchange(DateTime? id)
{
string url = ....;
try
{
ExchangeToDatabase etd = new ExchangeToDatabase(username, password, domain, url, id);
etd.ExportFromExchange();
}
catch (InvalidDateException ex)
{
return RedirectToAction("Display", "Error", new { returnUrl = "/", Message = ex.Message });
}
And tried with threads also:
/*System.Threading.Tasks.Task.Factory.StartNew(() =>
{
ExchangeToDatabase etd = new ExchangeToDatabase("cbstest", "ch#rteRsmarter", "vlaeynatie", url, id);
etd.ExportFromExchange();
});
or: doesn't work..
new Thread(() =>
{
ExchangeToDatabase etd = new ExchangeToDatabase("cbstest", "ch#rteRsmarter", "vlaeynatie", url, id);
etd.ExportFromExchange();
}).Start();*/
return Redirect("/");
}
After days of research and trying whatever possible to prevent blocking of the UI, I found an answer: make the user session readonly. An answer from #SamStephens in this post gave me the result that I want.
Here it is:
[SessionState(SessionStateBehavior.ReadOnly)]
I've done this recently for a project and I used Task.Run()
Task.Run(() => SomeMethod(someVariable));
I have an async Action that gets called by jquery ajax request:
View:
$.ajax({
url: "#Url.Action("StartVerification", "Devices")",
global: false,
data: JSON.stringify(machineIds),
contentType: 'application/json',
type: 'POST'
...
Controller:
[HttpPost]
[SessionExpireFilter(Order = 1)]
[CheckPermissions(Order = 2)]
[AjaxMessagesFilter(Order = 3)]
[AsyncTimeout(30000, Order = 4)]
[HandleError(ExceptionType = typeof(TimeoutException), View = "TimeoutError", Order = 5)]
public async Task<JsonResult> StartVerification(ICollection<Machine> machines)
{
Dictionary<int, bool> collection = new Dictionary<int, bool>();
foreach (var machine in machines)
{
Response response = new Response();
try
{
response = await this.deviceRepository.StartVerification(machine);
}
catch (Exception ex)
{
response.Success = false;
}
collection.Add(machine.MachineID, response.Success);
}
return this.Json(collection.ToDictionary(x => x.Key.ToString(), y => y.Value));
}
Web service call:
public async Task<Response> StartVerification(Machine machine, CancellationToken cancelToken = default(CancellationToken))
{
WebService WebServiceForTask = WebServiceFactory.NewInstance;
return await Task.Run(() => WebServiceForTask.StartVerificationForWebSite(machine.SiteID, machine.MachineID));
}
The problem I'm having is that when StartVerification action is executed which calls then queries a web service. The query for that result may take up to several seconds during which time a user may press a refresh button of their browser. What's the best way to handle this scenario and simply abort the call etc.
EDIT:
Maybe I'm asking the question wrong. The issue here is that when I StartVerification and hit refresh page F5 the page will NOT refresh until I get a response from webservice...and it looks like Action is not run async. I want it to work so that if a controller action is already called and waiting on a response from webservice I still should be able to simply browse away from the page that I'm calling the action from.
What's the best way to handle this scenario and simply abort the call etc
You could subscribe to the onbeforeunload event before you start the AJAX request:
window.onbeforeunload = function() {
return 'There\'s an ongoing operation. If you leave this page you might lose some data';
};
and when the AJAX call completes remove the subscription to this event.
Since you have an AsyncTimeout attribute, you should take a CancellationToken that represents that timeout.
There is another CancellationToken that represents a user disconnecting early (Response.ClientDisconnectedToken). However, there is currently a race condition on ClientDisconnectedToken so I do not recommend using it with the current release of ASP.NET (4.5). The best policy right now is to honor the AsyncTimeout and just ignore early client disconnects.
However, if you really wanted to detect client disconnect, you could periodically poll for Response.IsClientConnected.