I have a rest endpoint on asp.net mvc:
[HttpGet]
public List<SomeEntity> Get(){
// Lets imagine that this operation lasts for 1 minute
var someSlowOperationResult = SomeWhere.GetDataSlow();
return someSlowOperationResult;
}
On the frontEnd I have a next javascript:
var promise = $.get("/SomeEntities");
setTimeout(function(){promise.abort()}, 100);
How to force Thread to die after abort call, to prevent slow calculation to be done?
Thanks in advance.
I found that Response have isClientConnected property. So we can use next approach:
[HttpGet]
public List<SomeEntity> Get(){
var gotResult = false;
var result = new List<SomeEntity>();
var tokenSource2 = new CancellationTokenSource();
CancellationToken ct = tokenSource2.Token;
Task.Factory.StartNew(() =>
{
// Do something with cancelation token to break current operation
result = SomeWhere.GetSomethingReallySlow();
gotResult = true;
}, ct);
while (!gotResult)
{
if (!Response.IsClientConnected)
{
tokenSource2.Cancel();
return result;
}
Thread.Sleep(100);
}
return result;
}
Can we? Or I miss something?
UPDATE:
Yes, it works
The backend has no idea that you have called abort() and if the request has already been sent then the server-side logic will run until it completes. In order to stop it from running you will have to send another request to your controller which notifies that controller that you've aborted the request and the controller will have to access the instance that is currently running you slow operation and this instance should have a method which forces the calculations to cancel.
Related
I created a timeout middleware that works basically like this:
public async Task InvokeAsync(HttpContext httpContext)
{
var stopwatch = Stopwatch.StartNew();
using (var timeoutTS = CancellationTokenSource.CreateLinkedTokenSource(httpContext.RequestAborted))
{
var delayTask = Task.Delay(config.Timeout);
var res = await Task.WhenAny(delayTask, _next(httpContext));
Trace.WriteLine("Time taken = " + stopwatch.ElapsedMilliseconds);
if (res == delayTask)
{
timeoutTS.Cancel();
httpContext.Response.StatusCode = 408;
}
}
}
In order to test it, I created a controller action:
[HttpGet]
public async Task<string> Get(string timeout)
{
var result = DateTime.Now.ToString("mm:ss.fff");
if (timeout != null)
{
await Task.Delay(2000);
}
var rng = new Random();
result = result + " - " + DateTime.Now.ToString("mm:ss.fff");
return result;
}
The configured timeout to 500ms and the Time Taken reported is usually 501-504 ms (which is a very acceptable skid).
The problem is that every now and then I was seeing an error on the output windows saying that the response had already started. And I thought to myself: this cant be! this is happening 1 second earlier than the end of the Task.Delay on the corresponding controller.
So I opened up fiddler and (to my surprise) several requests are returning in 1.3-1.7 seconds WITH A FULL RESPONSE BODY.
By comparing the reported time written on the response body with the timestamp on fiddler "statistic" tab I can guarantee that the response im looking at does not belong to that request at hand!
Does anyone knows what's going on? Why is this "jumbling" happening?
Frankly, you're not using middleware in the way it is designed for.
You might want to read this middleware docs.
The ASP.NET Core request pipeline consists of a sequence of request delegates, called one after the other.
In your case, your middleware is running in parallel with the next middleware.
When a middleware short-circuits, it's called a terminal middleware because it prevents further middleware from processing the request.
If I understand you correctly, you might want to create such terminal middleware, but clearly your current one is not.
In your case, you have invoked the _next middleware, which means the request has already handed off to the next middleware in the request pipeline. The subsequent middleware components can start the response before the timeout has elapsed. i.e. a race condition between your middleware and a subsequent middleware.
To avoid the race condition, you should always check HasStarted before assigning the status code. And if the response has started, all you can do might only be aborting the request if you don't want the client to wait for too long.
static void ResetOrAbort(HttpContext httpContext)
{
var resetFeature = httpContext.Features.Get<IHttpResetFeature>();
if (resetFeature is not null)
{
resetFeature.Reset(2);
}
else
{
httpContext.Abort();
}
}
app.Use(next =>
{
return async context =>
{
var nextTask = next(context);
var t = await Task.WhenAny(nextTask, Task.Delay(100));
if (t != nextTask)
{
var response = context.Response;
// If response has not started, return 408
if (!response.HasStarted)
{
// NOTE: you will still get same exception
// because the above check does not eliminate
// any race condition
try
{
response.StatusCode = StatusCodes.Status408RequestTimeout;
await response.StartAsync();
}
catch
{
ResetOrAbort(context);
}
}
// Otherwise, abort the request
else
{
ResetOrAbort(context);
}
}
};
});
This question already has answers here:
An async/await example that causes a deadlock
(5 answers)
Closed 5 years ago.
I'm trying to make a call to another API using POST methods through Asynctask.
Everything works fine (the request is correctly executed because it launches my SQL request) but I can't get any response from the external server.
I can see that I don't need to run the task to make it execute but I don't have any result.
[HttpPost]
public string Post([FromBody]JObject value)
{
if (MesureController.CheckJsonIntegrity<Mesure>(value))
{
var task = MesureAsync(value);
return task.Result;
}
return null;
}
static async Task<string> MesureAsync(JObject value)
{
using (client)
{
string url = ConfigurationManager.AppSettings["internalserver:url"];
var json_string = new Dictionary<string, string>
{
{"json_string", value.ToString() }
};
var content = new FormUrlEncodedContent(json_string);
var response = await client.PostAsync(url + "Mesure/Index", content);
string resultContent = await response.Content.ReadAsStringAsync();
return resultContent;
}
}
You're seeing a common deadlock that I describe in more detail on my blog. In summary, ASP.NET (full, not Core) code runs within a "request context" that only allows one thread to work on a request at a time. When MesureAsync sends the POST to the other API, it returns an incomplete task. Your Post method then blocks the current thread, waiting for that task. Later, when the POST to the other API completes, MeasureAsync attempts to resume executing within that same request context, but it can't because Post has blocked a thread within that request context, and the request context only allows one thread at a time.
So, you end up with Post taking up the request context waiting for MeasureAsync to complete, and MeasureAsync waiting for Post to give up the request context so that it can complete. Classic deadlock.
The best solution is to go "async all the way", i.e., don't block on async code. In this case, replace Result with await:
[HttpPost]
public string Post([FromBody]JObject value)
{
if (MesureController.CheckJsonIntegrity<Mesure>(value))
{
return await MesureAsync(value);
}
return null;
}
If you try to compile this now, it will give you a compiler error that tells you exactly what to do with Post to get it to work:
[HttpPost]
public async Task<string> Post([FromBody]JObject value)
{
if (MesureController.CheckJsonIntegrity<Mesure>(value))
{
return await MesureAsync(value);
}
return null;
}
...and you're done!
No result was return in the original code because of the mixing of blocking and async code.
The actions syntax should be updated to be async all the way and also to allow better content negotiation.
Assuming this code is for Asp.Net-Core, the actions would be updated to
[HttpPost]
public async Task<IActionResult> Post([FromBody]JObject value) {
if (MesureController.CheckJsonIntegrity<Mesure>(value)) {
var measure = await MesureAsync(value);
return Ok(measure);
}
return BadRequest();
}
If using WebAPI 2.* then change IActionResult to IHttpActionResult and it will work the same way.
You have to ensure Task will run.
Use this snippet:
Task.Run(()=>MesureAsync(value)).Result;
I need to integrate a third party's Web API methods into a WCF service.
The WCF Service will be called by another external party, and they expect the call and return to be synchronous.
I am able to get results from the API with the usual RestClient.ExecuteAsync.
I put together the following for synchronous calls:
public static List<Books> GetSyncBooks(int companyId)
{
var response = GetSynchronousBooks(companyId);
var content = response.Result.Content;
List<Books> result = new List<Books>();
return Helpers.JSONSerialiser.Deserialize<BookList>(content);
}
async private static Task<IRestResponse> GetSynchronousBooks(int companyId)
{
var request = BuildGETRequest("Book", companyId);
var response = await RestSharpHelper.ExecuteSynchronousRequest(request);
return response;
}
public static Task<IRestResponse> ExecuteSynchronousRequest(RestRequest request)
{
var client = new RestClient(BaseUrl);
client.AddHandler("application/json", new RestSharpJsonDotNetDeserializers());
var tcs = new TaskCompletionSource<IRestResponse>(TaskCreationOptions.AttachedToParent);
client.ExecuteAsync(request, (restResponse, asyncHandle) =>
{
if (restResponse.ResponseStatus == ResponseStatus.Error)
tcs.SetException(restResponse.ErrorException);
else
tcs.SetResult(restResponse);
});
return tcs.Task;
// BREAKPOINT here shows TASK value as
// Id = 1, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
}
The problem, however, is that I never get a result using this. The response content is always null. What am I doing wrong?
EDIT: Thanks Stephen. I have seen your name on some of the questions here on this subject: your score seems to indicate you know your way around this. I have indeed implemented the wcf service calls as you indicated based on your answer at another question. Can I ask you a related follow-up question? How scalable are async WCF service calls like this example? Would it "just work" for multiple simultaneous calls in the range of 10 to 100 per second, ignoring the processing overhead downstream?
I assume that your WCF service is hosted in ASP.NET.
Your problem is here: response.Result. I explain this deadlock situation on my blog. In summary, await will capture the current "context" (in this case, an ASP.NET request context) and will use that to resume the async method. However, the ASP.NET request context only allows one thread at a time, so if the request thread is blocked (calling response.Result), then the async method can never continue and you get a deadlock.
The solution is to correct this misunderstanding:
The WCF Service will be called by another external party, and they expect the call and return to be synchronous.
Since you're dealing with a client/server scenario, you don't have to make it synchronous. The asynchrony of the client is completely independent from the asynchrony of the server.
So, just implement your WCF service asynchronously:
public static Task<List<Books>> GetBooksAsync(int companyId)
{
var response = await GetBooksAsync(companyId);
var content = response.Content;
List<Books> result = new List<Books>();
return Helpers.JSONSerialiser.Deserialize<BookList>(content);
}
The client can still call it synchronously if they wish to.
I was working with Asp MVC 3, and in my application was created Async controller with some methods like:
public void ActionAsync()
{
AsyncManager.OutstandingOperations.Increment();
AsyncManager.Parameters["someResult"] = GetSomeResult();
AsyncManager.OutstandingOperations.Decrement();
}
public JsonResult ActionCompleted(SometResultModel someResult)
{
return Json(someResult, JsonRequestBehavior.AllowGet);
}
And now, when I'm working with MVC4 and Web Api I need to create controller with async actions like in mvc 3. At current time it's looks like:
public Task<HttpResponseMessage> PostActionAsync()
{
return Task<HttpResponseMessage>.Factory.StartNew( () =>
{
var result = GetSomeResult();
return Request.CreateResponse(HttpStatusCode.Created, result);
});
}
Is it a good idea to make async actions in web api like this or there some better way is exist?
UPD. Also, if I will use
public async Task<HttpResponseMessage> ActionAsync()
{
var result = await GetSomeResult();
return Request.CreateResponse(HttpStatusCode.Created, result);
}
will this full action work in background thread? And how to make my GetSomeResult() function be awaitable? It's return that Task<HttpResponseMessage> is not awaitable.
There is a big difference with your original action in MVC 3 where you are basically releasing the client after the ActionAsync method is invoked (The client thread is released, and it has to call the ActionCompleted action afterward to get the results). If that's what you are looking for, you need to implement async code with tasks on the client side.
Your second version is to make the server code async but the client thread will still wait for a response synchronously. await GetResult will make the server thread to return to the ASP.NET thread pool until the GetResult method returns something, so that thread can be reused with another request. It does not have anything to do with background work. If you want to use a fire and forget approach, you need to use the Task.Factory.StartNew(() => your code) or ThreadPool.QueueUserWorkItem(() => your code)
i want a thing in JsonResult that they respond after the got request from the browser [client side] and respond them quickly before done the task.
means request come respond before task done and run a thread to done the task.
can anyone show me the code for doing that in asp.net MVC
Isn't AJAX sufficient for your scenario?
$.getJSON('#Url.Action("Foo")', function(result) {
// once the task completes this callback will be executed
});
// flow continues to execute normally
and on the server side:
public ActionResult Foo()
{
// TODO: some task
return Json(someResult, JsonRequestBehavior.AllowGet);
}
If this task is I/O intensive you could take advantage of asynchronous controllers and I/O Completion Ports.
If you have a fire-and-forget scenario you could simply start the task and return immediately from the controller:
public ActionResult StartTask()
{
// Fire the task
Task.Factory.StartNew(() =>
{
// TODO: the task goes here
// Remark: Ensure you handle exceptions
});
// return immediately
return Json(
new { Message = "Task started" },
JsonRequestBehavior.AllowGet
);
}