I'm trying to write an async page in asp .net which runs a function in a different thread.
The problem in the following code, is that when I debug it, the function EndAsyncOperation is never called. As a result, the page isn't fully loaded and loads for ever.
I use Action to run the code in a different thread from the thread pool. Is there maybe another way of running the code in a different thread that works?
Where am I going wrong?
And another question. I read that in ASP .Net the pages are ran with a threadpool. So howcome when I debug my site and try to load a few pages together they are loaded one after another syncronously?
public partial class AsyncPage : System.Web.UI.Page
{
void Page_Load(object sender, EventArgs e)
{
AddOnPreRenderCompleteAsync(
new BeginEventHandler(BeginAsyncOperation),
new EndEventHandler(EndAsyncOperation)
);
}
IAsyncResult BeginAsyncOperation(object sender, EventArgs e,
AsyncCallback cb, object state)
{
Action action = () =>
{
Start();
};
IAsyncResult asyncResult = action.BeginInvoke(new AsyncCallback(action.EndInvoke), null);
return asyncResult;
}
void EndAsyncOperation(IAsyncResult ar)
{
// This function isn't reached
}
public void Start()
{
// Do something
}
}
I believe that you need to pass the AsyncCallback object provided to you in the BeginAsyncOperation method parameters to BeginInvoke, not create a new one.
Your pages are loading synchronously because of your session configuration.
Access to ASP.NET session state is exclusive per session, which means that if two different users make concurrent requests, access to each separate session is granted concurrently. However, if two concurrent requests are made for the same session (by using the same SessionID value), the first request gets exclusive access to the session information. The second request executes only after the first request is finished. (The second session can also get access if the exclusive lock on the information is freed because the first request exceeds the lock time-out.) If the EnableSessionState value in the # Page directive is set to ReadOnly, a request for the read-only session information does not result in an exclusive lock on the session data. However, read-only requests for session data might still have to wait for a lock set by a read-write request for session data to clear.
Source: ASP.NET Session State Overview, my highlight.
Related
I learned that the preferred ASP.NET-Core-way for background tasks is a hosted service.
Is there any way to pass the (user) session to this background worker? Usually the session is part of the HttpContext which will not be available in the background task...
I know that I could persist data by writing to the database but I'd also like to cache some data in memory.
If this is all happening in-process, then you should be able to just pass the ISession object directly to your background service and have it interact with the user’s session without the user’s HttpContext having to be around.
The default session implementation is using the distributed cache to persist the information stored in the session. Each session object is then only interacted by with a session key that is stored in a user cookie. When the session object is being created, no direct or indirect reference to the HttpContext is being passed. The session store itself also does not access the HttpContext in some other way. The distributed cache itself is also independent of the context and also the DI service scope.
So at least in theory, this should work just fine. You can use the ISession object to modify the session and the next time the user makes a request with their session id, the updated information will be there waiting for them.
There is no guarantee the user session will exist when executing the background task. Encapsulate the data your task requires from the user session and pass it to the background task.
As you know session is based on this user request. User owns the session!
So when we are talking about session, actually we are pointing to the end-user-request received from the client! Imagine a situation that you have a background task in an aspnetcore-based micro-service with no user requests. Never ever you wont see any sessions to capture because there is no user to send any request.
In a normal sunny day, the probability of user session existence inside a background task is very low.
BUT!
If you have a background service that you want to use it as a cache, you should execute cache read/write operations per user request. I highly recommend you to avoid using HttpContext inside your background task, because your task will be non-extendable, tightly-coupled with http infrastructure.
There is a simple SAMPLE :D to be more clarified:
public interface ICache {
Task Write(string uniqueIdentifier, object data);
Task<object> Read(string uniqueIdentifier);
}
public class BackgroundTaskBasedCache : ICache {
public void Init()
{
//Initialize your background operations.
}
//IO bound
public async Task Write(string sessionId, object data)
{
//write inside your cache.
}
public async Task<object> Read(string sessionId)
{
//read from your cache.
return new object();//for test.
}
}
startup.cs:
//this will add a signleton background task. (you can do it manually or using other tools/strategies.
services.AddSingleton<ICache, BackgroundTaskBasedCache>();
inside your controller:
{
public TestController(ICache cache, IHttpContextAccessor){//fill properties}
public async Task<IActionResult> ExecuteSomeRequestAction()
{
await cache.Write(httpContextAccessor.HttpContext.SessionId, data);
}
}
hope to understand and best regards :)
According to the ASP.Net Core docs, the behaviour of the session state has changed in that it is now non-locking:
Session state is non-locking. If two requests simultaneously attempt to modify the contents of a session, the last request overrides the first. Session is implemented as a coherent session, which means that all the contents are stored together. When two requests seek to modify different session values, the last request may override session changes made by the first.
My understanding is that this is different to the behaviour of the session in the .Net Framework, where the user's session was locked per request so that whenever you read from/wrote to it, you weren't overwriting another request's data or reading stale data, for that user.
My question(s):
Is there a way to re-enable this per-request locking of the user's session in .Net Core?
If not, is there a reliable way to use the session to prevent duplicate submission of data for a given user? To give a specific example, we have a payment process that involves the user returning from an externally hosted ThreeDSecure (3DS) iFrame (payment card security process). We are noticing that sometimes (somehow) the user is submitting the form within the iFrame multiple times, which we have no control over. As a result this triggers multiple callbacks to our application. In our previous .Net Framework app, we used the session to indicate if a payment was in progress. If this flag was set in the session and you hit the 3DS callback again, the app would stop you proceeding. However, now it seems that because the session isn't locked, when these near simultaneous, duplicate callbacks occur, thread 'A' sets 'payment in progress = true' but thread 'B' doesn't see that in time, it's snapshot of the session is still seeing 'payment in progress = false' and the callback logic is processed twice.
What are some good approaches to handling simultaneous requests accessing the same session, now that the way the session works has changed?
The problem that you have faced with is called Race Condition (stackoverflow, wiki). To cut-through, you'd like to get exclusive access to the session state, you can achieve that in several ways and they highly depend on your architecture.
In-process synchronization
If you have a single machine with a single process handling all requests (for example you use a self-hosted server, Kestrel), you may use lock. Just do it correctly and not how #TMG suggested.
Here is an implementation reference:
Use single global object to lock all threads:
private static object s_locker = new object();
public bool Process(string transaction) {
lock (s_locker) {
if(!HttpContext.Session.TryGetValue("TransactionId", out _)) {
... handle transaction
}
}
}
Pros: a simple solution
Cons: all requests from all users will wait on this lock
use per-session lock object. Idea is similar, but instead of a single object you just use a dictionary:
internal class LockTracker : IDisposable
{
private static Dictionary<string, LockTracker> _locks = new Dictionary<string, LockTracker>();
private int _activeUses = 0;
private readonly string _id;
private LockTracker(string id) => _id = id;
public static LockTracker Get(string id)
{
lock(_locks)
{
if(!_locks.ContainsKey(id))
_locks.Add(id, new LockTracker(id));
var res = _locks[id];
res._activeUses += 1;
return res;
}
}
void IDisposable.Dispose()
{
lock(_locks)
{
_activeUses--;
if(_activeUses == 0)
_locks.Remove(_id);
}
}
}
public bool Process(string transaction)
{
var session = HttpContext.Session;
var locker = LockTracker.Get(session.Id);
using(locker) // remove object after execution if no other sessions use it
lock (locker) // synchronize threads on session specific object
{
// check if current session has already transaction in progress
var transactionInProgress = session.TryGetValue("TransactionId", out _);
if (!transactionInProgress)
{
// if there is no transaction, set and handle it
HttpContext.Session.Set("TransactionId", System.Text.Encoding.UTF8.GetBytes(transaction));
HttpContext.Session.Set("StartTransaction", BitConverter.GetBytes(DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
// handle transaction here
}
// return whatever you need, here is just a boolean.
return transactionInProgress;
}
}
Pros: manages concurrency on the session level
Cons: more complex solution
Remember that lock-based option will work only when the same process on the webserver handling all user's requests - lock is intra-process synchronization mechanism! Depending on what you use as a persistent layer for sessions (like NCache or Redis), this option might be the most performant though.
Cross-process synchronization
If there are several processes on the machine (for example you have IIS and apppool is configured to run multiple worker processes), then you need to use kernel-level synchronization primitive, like Mutex.
Cross-machine synchronization
If you have a load balancer (LB) in front of your webfarm so that any of N machines can handle user's request, then getting exclusive access is not so trivial.
One option here is to simplify the problem by enabling the 'sticky session' option in your LB so that all requests from the same user (session) will be routed to the same machine. In this case, you are fine to use any cross-process or in-process synchronization option (depends on what you have running there).
Another option is to externalize synchronization, for example, move it to the transactional DB, something similar to what #HoomanBahreini suggested. Beware that you need to be very cautious on handling failure scenarios: you may mark your session as in progress and then your webserver which handled it crashed leaving it locked in DB.
Important
In all of these options you must ensure that you obtain lock before reading the state and hold it until you update the state.
Please clarify what option is the closest to your case and I can provide more technical details.
Session is designed to store temporary user data among multiple requests, a good example is login-state... without session you would have to login to stackoverflow.com every time you open a new question... but the website remembers you, because your send them your session state inside a cookie. According to Microsoft:
The session data is backed by a cache and considered ephemeral data.
The site should continue to function without the session data.
Critical application data should be stored in the user database and
cached in session only as a performance optimization.
It is quite simple to implement a locking mechanism to solve your mutex issue, however the session itself is not a reliable storage and you may loose its content at any time.
How to identify duplicate payments?
The problem is you are getting multiple payment requests and you want to discard the duplicate ones... what's your definition of a duplicate payment?
Your current solution discard the second payment while a first one is in progress... let's say your payment takes 2 seconds to complete... what will happen if you receive the duplicate payment after 3 seconds?
Every reliable payment system includes a unique PaymentId in their request... what you need to do is to mark this PaymentId as processed in your DB. This way you won't process the same payment twice, no matter when the duplicate request arrives.
You can use a Unique Constraint on PaymentId to prevent duplicate payments:
public bool ProcessPayment(Payment payment) {
bool res = InsertIntoDb(payment);
if (res == false) {
return false; // <-- insert has failed because PaymentId is not unique
}
Process(payment);
return true;
}
Same example using lock:
public class SafePayment {
private static readonly Object lockObject = new Object();
public bool ProcessPayment(Payment payment) {
lock (lockObject) {
var duplicatePayment = ReadFromDb(payment.Id);
if (duplicatePayment != null) {
return false; // <-- duplicate
}
Process(payment);
WriteToDb(payment);
return true;
}
}
}
Net 4.5 Task-based Asynchronous Pattern (TAP) using web forms and user controls
There seems to some tricky differences between webforms, winforms, WCF and MVC approaches. So far I have:
In my <%# Page Async="true"
In Web Config:
appSettings -
add key="aspnet:UseTaskFriendlySynchronizationContext"
(This means I can use Tasks without having to register them in my page?)
My User control will call a web service to get a User profile based on a Membership Number.
In my User Control I want to display a progress bar while the long running process proceeds. Since the actual length of the call will always be undetermined the timing of the progress bar will be a little faked (Currently 10 seconds to complete the task). I tried to use the Task IProgress reporting but I found it would only report the start and the end of the process in webforms, not intermediate steps, so I created a Javascript reporter for the UI and call ajax to a fake status processor. My Ajax responds with a % complete in a custom Response.AppendHeader(ProgressStatus, value);
So System.Web.HttpResponse HttpContext.Current is key to this but when I run my Task, HttpContext.Current gets blocked and the prog bar starts after the Task is complete. I Can't see where the Thread gets blocked?
Here is some code:
UserControl.ascx.cs
protected void Page_Load(object sender, EventArgs e)
{
Response.AddHeader("cache-control", "no-cache");
if (!Page.IsPostBack)
{
InitialiseControl();
}
}
public async void InitialiseControl() // Its an event
{
// wait for a command from the ajax
if (Request["startTask"] != null)
{
//..do some progress stuff
await GetUserProfileAsync(request.MembershipNumber);
//..Report back to the UI
}
if (Request["getStatus"] != null)
{
//..polling from the ajax, continue with progress reporting back to the UI
}
}
// we are still in HttpContext.Current so add object to session
private async Task GetUserProfileAsync(string membershipNumber)
{
UserProfile profile =
await WebApplication.Library.Methods.Membership.UserProfile.GetUsersProfileTaskAsync(membershipNumber);
HttpContext.Current.Session["UserProfile_" + membershipNumber] = profile;
}
WebApplication.Library.Methods.Membership.UserProfile:
public async static Task<UserProfile> GetUsersProfileTaskAsync(string usersMembershipNumber)
{
Task<UserProfile> t = new Task<UserProfile>
(
() =>
{
UserProfile profile = new UserProfile() { Name = "Some name",
Password = "TestPassword",
MembershipNumber = usersMembershipNumber };
return profile;
}
);
// start the task
t.Start();
int i = 0;
for (i = 0; i < 8; i++)
{
// Fake delay simulating a web service
Thread.Sleep(1000);
}
if (i == 8)
{
return await t;
}
return null;
}
Hi Stephen thanks for your reply. "I hope by this that you mean it sends a complete response"
My JavaScript picks up the response as a full header returned:
xmlhttp.getResponseHeader("ProgressStatus");
However Im not even sure my code is running asyncly. If I try to register my Task on Page Load:
this.Page.RegisterAsyncTask(new PageAsyncTask(() => GetUserProfileAsync(membershipNumber)));
(does this approach refer to the legacy usage for Async? and not the 4.5 Task usage)
I get Error: Task-returning Page methods are unsupported in the current application configuration. To work around this, remove the following configuration switch in Web.config:
add key="aspnet:UseTaskFriendlySynchronizationContext"
When I remove the switch: System.InvalidOperationException: An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%# Page Async="true" %>. This exception may also indicate an attempt to call an "async void" method, which is generally unsupported within ASP.NET request processing. Instead, the asynchronous method should return a Task, and the caller should await it.
This error occurs at InitialiseControl();
I think the first thing is to prove we can do async in a UserControl. This is the first time I have used any Async stuff and I have spent a lot of time watching the fantastic videos made by Lucian Wischik, but he never really covers webforms (which I have to use since my company will not move to MVC).
In Web Config: appSettings - add key="aspnet:UseTaskFriendlySynchronizationContext" (This means I can use Tasks without having to register them in my page?)
No; it means you can use async and await. More info.
In my User Control I want to display a progress bar while the long running process proceeds.
async won't help you here. The easiest approach is to use SignalR, which has built-in support for progress updates.
I created a Javascript reporter for the UI and call ajax to a fake status processor.
AJAX is an appropriate alternative solution.
My Ajax responds with a % complete in a custom Response.AppendHeader(ProgressStatus, value);
I hope by this that you mean it sends a complete response, not just flushing headers to your JavaScript.
I Can't see where the Thread gets blocked?
What I suspect is happening is that the session state is preventing the second request; see the "Concurrent Requests" section of the Session State Overview.
I've written a C# MVC 3 with NHibernate as the ORM and I'm having some odd exceptions thrown on most page loads. They seem to mostly relate to closed sessions and the like and I've checked most of the common issues but found little to help. Some of the exceptions include:
[[NHibernate.Util.ADOExceptionReporter]] : System.InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.
at System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand command)
[[NHibernate.Transaction.AdoTransaction]] : Begin transaction failed
System.Data.SqlClient.SqlException (0x80131904): The server failed to resume the transaction. Desc:3b00000006.
[[NHibernate.Transaction.AdoTransaction]] : Commit failed
System.NullReferenceException: Object reference not set to an instance of an object.
[[NHibernate.Transaction.AdoTransaction]] : Commit failed
System.Data.SqlClient.SqlException (0x80131904): The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
[[NHibernate.Util.ADOExceptionReporter]] : System.InvalidOperationException: The transaction is either not associated with the current connection or has been completed.
at System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean async)
[[NHibernate.Transaction.AdoTransaction]] : Begin transaction failed
System.InvalidOperationException: SqlConnection does not support parallel transactions.
I apologise for the wall of exceptions, I suspect they are related but there could potentially be another error in the code as well causing one or two.
I don't like to use the word random for these things but I can't seem to track down any specific line of code that calls them, they just seem to appear at lines of code relating to ISession objects. I have even had a "Session is closed" exception thrown on the BeginTranscation method in my Global.asax file.
The application uses the web option of current_session_context_class in hibernate.cfg.xml.
My suspicion is that it is related to my session management code. The website usually loads around 10 simultaneous AJAX requests and the errors seem to occur more often when multiple pages are loading at the same time. There are two session factories, one for each database being used.
Here is my relevant Global.asax code:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
_sessionFactory = (new WebClientSessionManager()).MakeSessionFactory();
_sessionFactoryNotWeb = ClientSessionManager.MakeSessionFactory();
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
_session = _sessionFactory.OpenSession();
_sessionNotWeb = _sessionFactoryNotWeb.OpenSession();
CurrentSessionContext.Bind(_sessionNotWeb);
CurrentSessionContext.Bind(_session);
_session.BeginTransaction();
_sessionNotWeb.BeginTransaction();
}
protected void Application_EndRequest(object sender, EventArgs e)
{
//Same code is repeated for the _sessionFactoryNotWeb
ISession session = CurrentSessionContext.Unbind(_sessionFactory);
if (session != null)
{
if (session.Transaction.IsActive)
{
try
{
session.Transaction.Commit();
}
catch
{
session.Transaction.Rollback();
}
}
try
{
session.Dispose();
}
catch
{
}
}
I have had a look at the page running in NHibernate profiler. Sometimes sessions are not started with BeginTranscation, sometimes they are not Committed, sometimes neither; and most puzzlingly, sometimes they are started three times but not finished.
Any calls to the ISession object are managed through this code (there is one for each factory):
public static ISession WebSession()
{
if (CurrentSessionContext.HasBind(MvcApplication._sessionFactory))
{
if (MvcApplication._sessionFactory.GetCurrentSession().IsOpen)
{
return MvcApplication._sessionFactory.GetCurrentSession();
}
else
{
log4net.LogManager.GetLogger(typeof(DBHandler)).Debug("Unbinding NHibernate session");
CurrentSessionContext.Unbind(MvcApplication._sessionFactory);
return WebSession();
}
}
else
{
log4net.LogManager.GetLogger(typeof(DBHandler)).Debug("Initialising NHibernate session");
var session = MvcApplication._sessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
session.BeginTransaction();
return session;
}
}
There are no calls to BeginTransaction or Commit throughout the app without them being flushed, committed, the session disposed of and then being reopened as per the code above.
Any light you guys could shed on this would be much appreciated!
You seem to store your session in a global (application wide) property in your Global.asax.cs. That means, the property contains the last session created, not the session for a specific request. Two requests at the same time and you don't even know if you still access the session you just created, because it might already have been overwritten by another session from the next request, running the same code. You shouldn't store your session somewhere unrelated to the web request if you want to follow a Session-per-Request pattern. You could for instance store your NH session in the HttpContext.Items-Collection. As another way to session management in MVC Ayende posted a nice example on how to wrap session management around a single MVC action instead of the whole request.
I have my own SQL based session class. I want to automatically commit the data in the session at the end of program execution (i.e. at the end of serving the page request). Do object destructors (finalizers) not get executed at the end of every request? Is there anyway of doing this instead of having to explicitly call a Dispose() method every time I am done with my session object?
One possibility is by using the global.asax. There is a handler for the end of a request, in C# the call inside global.asax.cs would look something list this:
protected void Application_EndRequest(object sender, EventArgs e){
//perform action - you have access to the HttpContext
}
You have to watch out for what is going through the handler though - depending on your configuration your assets (stylesheet, images, etc.) may also be hitting this request so you may have to devise a way to ensure only your pages have the actions taken on them.
In C#, finalizers are non-deterministic, which means you don't have any guarantee when it will be executed. So, no, you can't use them for your scenario.
I see two ways to approach this:
Programmatically calling Dispose at some point towards the end of the page's life cycle (or via the global.asax as mentioned by Michael G.).
Have your SQL based session class wire itself up to a page event (such as the Unload event) to do whatever actions it wants to clean itself up.
UPDATE:
In response to your question about suggestion #2, I will expound a bit. This option would be to somehow allow your SQL based session class to gain a refernce to the web page instance. One option (which I'll show below) would be to use the constructor of the SQL based session class to obtain a reference to the web page. Then the SQL based session class can sign up for any event that it desires in order to know what is happening to the page.
namespace SomeNamespace
{
using System.Web.UI;
public class SqlBasedSession
{
public SqlBasedSession(Page webPage)
{
webPage.Unload += new EventHandler(webPage_Unload);
}
void webPage_Unload(object sender, EventArgs e)
{
// the web page is being unloaded so this class can
// cleanup it's resources now
}
}
}
Let me know if this still isn't clear.
I would suggest to go with IOC container provider for these kind of stuff. You can hook them up, to the end of a request and execute some code, especially good for some transactional stuff.
I have been using Autofac, it has OnActivated() method, which I use to hook my transaction commits to on activation of session.