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.
Related
I am using .NET Core 3.1. I am wondering if HttpContext.Session.SetString(...) is thread-safe? For example, client makes two requests to the same controller action (Test) at the same time. Controller adds a string to the session (see example below). Will there be two, one or zero keys in the session at the end (ex. when I refresh the page)?
public IActionResult Test()
{
HttpContext.Session.SetString(Guid.NewGuid().ToString(), "test");
return Ok();
}
I am saving some values to session when using DevExtreme FileUploader. When I upload multiple files at once, component makes multiple requests at the same time and at the end, there are usually some keys missing from the session. I think there is some race condition going on.
ADDED: CLIENT CODE
I noticed that session keys are missing only if I use method: 'POST' (there is only 1 key). If I use method: 'GET', there are 3 keys (correct).
$(document).ready(function () {
var method = 'GET'; // works (3 keys)
//var method = 'POST'; // doesn't work (1 key)
$('#fire').on('click', function () {
$.when(
$.ajax({
url: 'Session/Test',
method: method,
success: function(){
console.log('response', 1);
}
}),
$.ajax({
url: 'Session/Test',
method: method,
success: function(){
console.log('response', 2);
}
}),
$.ajax({
url: 'Session/Test',
method: method,
success: function(){
console.log('response', 3);
}
})
).then(function () {
alert('Done');
});
});
});
Assume you use the default session implementation offered by ASP.Net Core.
In terms of HttpContext.Session:
HttpContext.Session returns an instance of DistributedSession, which internally uses a Dictionary<TKey, TValaue>. Dictionary is not thread safe, so if you access Session from multiple threads (e.g. Task.Run), it can cause unexpected results.
In terms of Session for different requests:
In ASP.Net Core, Session comes from ISessionStore, which has a transient lifetime. Meaning, Session object is not shared by requests. So if you have concurrent requests, each of which will have its own Session object.
In terms of race condition:
The default implementation of session reads/writes session state from/to .AspNetCore.Session cookie. This may cause race conditions.
Because Session is per client, so you might have race conditions when you have concurrent requests from the same client touching same bits and pieces in the same cookie / session state. The race condition however is not because of Session on the server side. It is actually caused by cookie management at client side.
Session state is non-locking. If two requests simultaneously attempt to modify the contents of a session, the last request overrides the first.
Consider this example:
Say you have a controller action which sets Session with provided value, and another controller action retrieves the value from Session and returns it in body:
[HttpGet]
[Route("create")]
public IActionResult CreateSession([FromQuery]string value)
{
HttpContext.Session.SetString("key", value);
return Ok();
}
[HttpGet]
[Route("get")]
public IActionResult ReturnSession([FromQuery] string expected)
{
var actual = HttpContext.Session.GetString("key");
return Ok(new { actual, expected });
}
If you test these actions with an HttpClient:
async Task TestSession(HttpClient client, string str)
{
await client.GetAsync($"https://localhost:5001/session/create?value={str}");
var r = await client.GetAsync($"https://localhost:5001/session/get?expected={str}");
var session = await r.Content.ReadAsStringAsync();
Console.WriteLine(session);
}
using (var client = new HttpClient())
{
await TestSession(client, "abc");
}
The output should look like:
{"actual":"abc","expected":"abc"}
Problem raises when you have concurrent requests from the same client:
using (var client = new HttpClient())
{
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
{
var str = i.ToString();
tasks.Add(Task.Run(() => TestSession(client, str)));
}
await Task.WhenAll(tasks);
}
The output looks like:
{"actual":"2","expected":"1"}
{"actual":"3","expected":"6"}
{"actual":"4","expected":"5"}
{"actual":"4","expected":"3"}
{"actual":"4","expected":"2"}
{"actual":"8","expected":"4"}
{"actual":"7","expected":"8"}
{"actual":"7","expected":"7"}
{"actual":"9","expected":"0"}
{"actual":"9","expected":"9"}
In the above case, session state was changed by request 3, between create and get of request 6, meaning it is likely request 6 cannot see its session state correctly.
To avoid this issue, you could use different HttpClient for each batch.
No, dotnet collections are not thread safe, and neither is session.
If you are sharing the same session over more than one thread you should treat it as readonly
I'm a little new to ASP.Net and Asynchronous coding so bear with me. I have written an asynchronous wrapper in C# for a web API that I would like to use in a ASP.Net application.
Here is one of the functions in the C# API wrapper:
public async Task<string> getProducts()
{
Products products = new Products();
products.data = new List<Item>();
string URL = client.BaseAddress + "/catalog/products";
string additionalQuery = "include=images";
HttpResponseMessage response = await client.GetAsync(URL + "?" + additionalQuery);
if (response.IsSuccessStatusCode)
{
Products p = await response.Content.ReadAsAsync<Products>();
products.data.AddRange(p.data);
while (response.IsSuccessStatusCode && p.meta.pagination.links.next != null)
{
response = await client.GetAsync(URL + p.meta.pagination.links.next + "&" + additionalQuery);
if (response.IsSuccessStatusCode)
{
p = await response.Content.ReadAsAsync<Products>();
products.data.AddRange(p.data);
}
}
}
return JsonConvert.SerializeObject(products, Formatting.Indented);
}
I then have a WebMethod in my ASP.Net application (which will be called using Ajax from a Javascript file) which should call the getProducts() function.
[WebMethod]
public static string GetProducts()
{
BigCommerceAPI api = getAPI();
return await api.getProducts();
}
Now of course this will not work as the WebMethod is not an async method. I have tried to change it to an async method which looked like:
[WebMethod]
public static async Task<string> GetProducts()
{
BigCommerceAPI api = getAPI();
return await api.getProducts();
}
This code does run, but as soon as it gets to the HttpResponseMessage response = await client.GetAsync(URL + "?" + additionalQuery); line in the getProducts() function the debugger will stop without any errors or data being returned.
What am I missing? How can I get call this asynchronous API from my ASP application?
So I actually resolved an issue very similar to this last night. It's odd because the call worked in .net 4.5. But we moved to 4.5.2 and the method started deadlocking.
I found these enlightening articles (here, here, and here) on async and asp.net.
So I modified my code to this
public async Task<Member> GetMemberByOrganizationId(string organizationId)
{
var task =
await
// ReSharper disable once UseStringInterpolation
_httpClient.GetAsync(string.Format("mdm/rest/api/members/member?accountId={0}", organizationId)).ConfigureAwait(false);
task.EnsureSuccessStatusCode();
var payload = task.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Member>(await payload.ConfigureAwait(false),
new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
}
which resolved my deadlocking issue.
So TLDR: from the Stephen Cleary article
In the overview, I mentioned that when you await a built-in awaitable,
then the awaitable will capture the current “context” and later apply
it to the remainder of the async method. What exactly is that
“context”?
Simple answer:
If you’re on a UI thread, then it’s a UI context. If you’re responding
to an ASP.NET request, then it’s an ASP.NET request context.
Otherwise, it’s usually a thread pool context. Complex answer:
If SynchronizationContext.Current is not null, then it’s the current
SynchronizationContext. (UI and ASP.NET request contexts are
SynchronizationContext contexts). Otherwise, it’s the current
TaskScheduler (TaskScheduler.Default is the thread pool context).
and the solution
In this case, you want to tell the awaiter to not capture the current
context by calling ConfigureAwait and passing false
I am not sure what is [WebMethod] in ASP.NET. I remember it used to be SOAP web services but no one does it anymore as we have Web API with controllers where you can use async/await in action methods.
One way to test your code would be to execute async method synchronously using .Result:
[WebMethod]
public static string GetProducts()
{
BigCommerceAPI api = getAPI();
return api.getProducts().Result;
}
As maccettura pointed out in the comment, it's a synchronous call and it locks the thread. To make sure you don't have dead locks, follow Fran's advice and add .ConfigureAwait(false) at the end of each async call in getProducts() method.
First by convention GetProducts() should be named GetProductsAsync().
Second, async does not magically allocate a new thread for it's method invocation. async-await is mainly about taking advantage of naturally asynchronous APIs, such as a network call to a database or a remote web-service.
When you use Task.Run, you explicitly use a thread-pool thread to execute your delegate.
[WebMethod]
public static string GetProductsAsync()
{
BigCommerceAPI api = getAPI();
return Task.Run(() => api.getProductsAsync().Result);
}
Check this link It's a project sample about how to implement Asynchronous web services call in ASP.NET
I had a very similar issue:
Main webapp is a ASP.NET 4.5 Web forms, but many of its functions implemented as AJAX calls from UI to a [webMethod] decorated function in the aspx.cs code-behind:
The webmethod makes an async call to a proxy. This call was
originally implemented with Task.Run() and I tried to rewrite with
just await ...
[WebMethod]
public static async Task<OperationResponse<CandidatesContainer>> GetCandidates(string currentRoleName, string customerNameFilter, string countryFilter, string currentQuarter)
{
string htmlResult = String.Empty;
List<CandidateEntryDTO> entries = new List<CandidateEntryDTO>();
try
{
entries = await GetCandiatesFromProxy(currentUser, currentRoleName, customerNameFilter, countryFilter, currentQuarter)
.ConfigureAwait(false);
}
catch (Exception ex)
{
log.Error("Error .....", ex);
}
CandidatesContainer payloadContainer = new CandidatesContainer {
CountryMappedCandiates = ...,
GridsHtml = htmlResult };
return new OperationResponse<CandidatesContainer>(payloadContainer, true);
}
3) The call GetCandiatesFromProxy(...) is the top of a chain of several async methods and at the bottom there's finally a HttpClient.GetAsync(...) call:
private async Task<B2PSResponse<string>> GetResponseFromB2PService(string serviceURI)
{
string jsonResultString = String.Empty;
if (_httpClientHandler == null)
{
_httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true };
}
if (_client == null)
{
_client = new HttpClient(_httpClientHandler);
}
HttpResponseMessage response = await _client.GetAsync(serviceURI).ConfigureAwait(false);
HttpContent content = response.Content;
string json = String.Empty;
if (response.StatusCode == HttpStatusCode.OK)
{
json = await content.ReadAsStringAsync().ConfigureAwait(false);
}
B2PSResponse<string> b2psResponse = new B2PSResponse<string>(response.StatusCode, response.ReasonPhrase, json);
return b2psResponse;
}
The code was not working (was stuck on the lowest level await) until
I started to add .ConfigureAwait(false) to each await call.
Interesting, that I had to add these .ConfigureAwait(false) to all await calls on the chain - all the way to the top call in the webMethod. Removing any of them would break the code - it would hang after the await that does not have the .ConfigureAwait(false).
The last point: I had to modify the Ajax call's SUCCESS path. The default Jason serialization for webmethods makes the result sent to AJAX call as
{data.d.MyObject}
i.e. inserts the {d} field containing the actual payload. After the webmethod return value was changed from MyObject to Task - this no longer worked - my payload was not found in the {data.d}. The result now contains
{data.d.Result.MyObject}
This is simply the result of serializing the Task object - which has the .Result field.
With one small change to the AJAX call is now working.
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();
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));
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