Im using iTextSharp (not that this is relative to iTextSharp) to generate PDF bytes using code similar to the following:
Function GetFileBytes() as Byte()
Dim result() As Byte
Using ms As New MemoryStream
Dim reader As New PdfReader(pdfForm)
Dim writer As New PdfStamper(reader, ms)
Dim fields As AcroFields = writer.AcroFields
fields.SetField("Customer Name", custInfo.FirstName + " " + custInfo.LastName)
result = ms.GetBuffer
End Using
End Function
I want to make this an asynchronous task instead, returning Task(Of Byte()) instead, but I'm having problems figuring out how to make my memorystream write in an asynchronous mode, and how to return a Task(Of Byte()) in the process. Any tips, suggestions? Thanks!
EDIT 1
I wanted to clarify the need for using a Task and using this code, as it seems some comments so far are along the lines of "why are you doing it this way" and "no just do this instead, and I want to politely say "thanks, but I want to do it this way" :)
Here's my scenario, imagine a website that collects data on a few pages, then presents a review screen, and then gives the user the ability to view a PDF containing that data. On the review screen I need to generate a 1 MB PDF file, upload it to BLOB storage, and have it available when the user chooses to download it.
I want to generate and upload the PDF while the user is reviewing the review screen and not when they choose the "view PDF" button. That code will be done using my example, which is why I'm curious how to create a Task of type Byte(). Any help is always appreciated, thanks again!
if you really want to make is async a simpler approach would be to create a private thread in the class, that thread on page_init will start the process then on Page_LoadComplete the thread would wait using thread.join() and then you would get the function result, but as mentioned the relative speed gain is minimal, consider that threads(or tasks) are really expensive
Option 2
create an ajax request to a generic handler that returns the pdf file data, this way no thread involved and better user experience
<%# WebHandler Language="VB" Class="GetFile" %>
Imports System
Imports System.Web
Imports System.IO
Public Class GetFile : Implements IHttpHandler
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
context.Response.Clear()
If File.Exists(filePath & docName) Then
context.Response.AddHeader("content-disposition", "attachment; filename=" & HttpContext.Current.Server.UrlEncode(docName))
context.Response.ContentType = "text/pdf"
context.Response.WriteFile(filelocation & docName) 'in your case this writes the bites of the generated file, so instead of the path just pass in the data
End If
context.Response.Flush()
context.Response.End()
End Sub
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property
End Class
Responding to your edit: this scenario makes sense. Wrap everything in a Task.Run call. Be sure to be thread-safe (don't use the HttpContext in the task for example). Also log errors because they might be bugs.
Note, though, that with ASP.NET background work can be aborted at any time (e.g. when the worker process recycles). You have to tolerate suddenly disappearing background work. In your case the PDF might be missing when the user tries to access it. Also, what if it is still being generated when the user tries to download it. Must solve those issues.
Here's my scenario, imagine a website that collects data on a few pages, then presents a review screen, and then gives the user the ability to view a PDF containing that data. On the review screen I need to generate a 1 MB PDF file, upload it to BLOB storage, and have it available when the user chooses to download it.
Since you refer to "blob storage", I'll assume you're using Azure.
You need a worker role or service to do this reliably. The worker role should just be reading from an Azure queue and generating PDFs.
So, when your web role generates the review screen, before returning the response, it will also place that data into the Azure queue. When your web role is notified that the user accepts the review, then it should (asynchronously) wait until the PDF file is generated.
(Don't use this code below.. see end of "answer" for reasoning)..
Thanks to everyone for the help. In the end I was approaching this wrong. All I needed was to create an Async wrapper function to return my Task(Of Byte())
So if my initial code was this:
Function GetFileBytes() as Byte()
Dim result() As Byte
Using ms As New MemoryStream
Dim reader As New PdfReader(pdfForm)
Dim writer As New PdfStamper(reader, ms)
Dim fields As AcroFields = writer.AcroFields
fields.SetField("Customer Name", custInfo.FirstName + " " + custInfo.LastName)
result = ms.GetBuffer
End Using
End Function
Now, I created a new method called GetFileBytesAsync to return Task(Of Byte()) calling the same GetFileBytes method described above, it's just called using a Task.Factory.StartNew, like so:
Public Shared Async Function GetFileBytesAsync() As Task(Of Byte())
Dim t As Task(Of Byte()) = Task.Factory.StartNew(Function()
Return GetFileBytes()
End Function)
Return Await t
End Function
And in my calling function, I now just use the Result property of my task object to access the byte array. HTH!
EDIT: OK, so as Stephen Cleary aptly pointed out, I'm doing this all wrong, the above code is not recommended and it isn't doing anything asynchronously.. as a matter of fact its creating new threads to create some blocking code. Sorry...
I came across this TechEd talk: http://channel9.msdn.com/Events/TechEd/NorthAmerica/2013/DEV-B337 - it's How to Build ASP.NET Web Applications Using Async. At the 54 Minute Mark it actually indicates that trying to use a "background thread" on an ASP.NET Web Server is wrong, as there are no background threads for use in IIS, and in essence you are taking up threads that could\should be used to respond to web requests.
Related
In short, remaining in the HTTP context, I would like the user, after clicking on an order completion button, not to wait for the mails to be sent before being sent back to a "thak you page".
I saw that HostingEnvironment.QueueBackgroundWorkItem could help me with this but there is always the risk of it being killed by IIS recycling.
What is the best solution to do this?
I know the best would be to develop a separate console solution but it wouldn't be worth it for 3/4 emails, alternatively I could consider speeding it up by making them asynchronous?
Protected Sub btnConcludiOrdine_Click(sender As Object, e As System.EventArgs) Handles btnConcludiOrdine.Click
If IsValidOrder(Me._cart, msg) Then
If Me._cart.SaveOrder(Me._user, Me._orderCode, Me._lang) then
'Update quantity in db
Dim mail As New EmailBLL
mail.SendOrderNotice(Me._cart, Me._lang) '2 Mails
mail.SendProductNotice() '2 Mails
End If
Else
Response.Redirect("*Error URL*")
End If
End Sub
The way you approach this is as suggested – start a task, or so called new processor thread.
So, what you would do is break out the code – this even works if code behind is for a web form.
So, the first step is to move out the “slow” parts, or the parts we want to run separate.
The main issue is that to start/launch/want/desire/achieve a brand new processor thread?
The sub call CAN ONLY PASS ONE “parameter” and the sub can only accept one parameter!!!!!
I note in your case that routine needs two values.
However, that “one parameter” can be a array of “many” values, or even a collection or whatever. In our case we pass the two values.
So just keep in mind that what you call can NOT update or “use” the values of controls on the form – the instance of that form will go out of scope.
But we can of course PASS the values you need. This will allow that routine to run 100% independent of the web form.
I also VERY strong suggest that if you DO place the sub in the same web page code behind? You should/can mark that sub as shared. Doing so will allow the compiler to get mad at you and spit out errors if that routine say tries to use or update a control value on the form.
However, it is MUCH better is to place this sub in a separate standard code module out side of the web forms code behind.
Regardless of above, we can now re-write the code we have as this:
If Me._cart.SaveOrder(Me._user, Me._orderCode, Me._lang) then
Dim myInfo(1) as object
myInfo(0) = me.cart
myInfo(1) = me_._lng
Call MyUpdateQ(myInfo)
End If
' bla bla lba
Shared Sub MyUPdateQ(p() as object)
'Update quantity in db
Dim mail As New EmailBLL
mail.SendOrderNotice(p(0),p(1)
mail.SendProductNotice() '2 Mails
End Sub
Ok, so far, we not achieved much, but we re-writing to accept the ONE array is KEY here.
So, now now make sure the above runs/works and is all happy.
Now, because we moved out the "work load" to that one routine, it is now a simple matter to start a thread.
Now, Our above code becomes this:
Protected Sub btnConcludiOrdine_Click(sender As Object, e As System.EventArgs) Handles btnConcludiOrdine.Click
If IsValidOrder(Me._cart, msg) Then
If Me._cart.SaveOrder(Me._user, Me._orderCode, Me._lang) then
Dim myInfo(1) as object
myInfo(0) = me.cart
myInfo(1) = me_._lng
Dim MyThread As New Thread(New ParameterizedThreadStart(AddressOf MyUpdateQ))
MyThread.Start(myInfo)
End If
Else
Response.Redirect("*Error URL*")
End If
End Sub
Shared Sub MyUPdateQ(p() as object)
'Update quantity in db
Dim mail As New EmailBLL
mail.SendOrderNotice(p(0),p(1)
mail.SendProductNotice() '2 Mails
End Sub
That is it. Now when you click your button it will wait ZERO time, since the long running routine is now going to run 100% as a separate thread. And this will also mean that when the user clicks the button - the page will respond instant and post back to user will be done. So if that thread takes 6 seconds, or even 25 seconds, the user will not notice this delay.
Just push your sending mail logic in Task and if you are not interested in result don't await it. c# syntax
Task.Run(() => SendEmail());
I've run across some code in an ASP.NET app, and I'm wondering whether there is any practical difference between the following two snippets. Note, we are trying to send a request to a 3rd party endpoint and then use the response in what's rendered on the page.
Dim asyncResult As IAsyncResult = request.BeginGetResponse(Nothing, Nothing)
asyncResult.AsyncWaitHandle.WaitOne()
Using webResponse As WebResponse = request.EndGetResponse(asyncResult)
Using rd As StreamReader = New StreamReader(webResponse.GetResponseStream())
'code here
End Using
End Using
and this synchronous version:
Using webResponse As WebResponse = request.GetResponse()
Using rd As StreamReader = New StreamReader(webResponse.GetResponseStream())
'code here
End Using
End Using
According to this answer to another question, WaitOne blocks the thread. If so, is there really any advantage to doing that versus just using the synchronous method above? Am I correct in assuming that the thread processing the page will not be available to process other requests until this the method is finished either way?
That is a common anti-pattern. You get the worst of both worlds. No threads are unblocked and you add overhead.
Probably, the responsible person heard that using async APIs makes their app more scalable. If that was the case, why wouldn't GetResponse be just implemented in terms of the Begin/End methods and always be scalable?
Async is all the rage at the moment and it is being misused all the time and even when used correctly it is often a waste of time on the server. Don't be surprised seeing stuff like this.
I am an absolute beginner on ASP.net (VB.) Please pardon me if the question is too obvious for the experienced members.
I tried to make a simple WebRequest in the async mode in case the target URL takes long to provide the data. In my code below, I just want to see if the callback block (RespCallback) is called correctly every time. If all goes well, lblResult should have the string '123' appended to it every time I click the button which calls the 'GetData' sub.
However, the lblResult only shows 123 after the first click. After the subsequent click, the lblResult only gets appended with '12', as if RespCallback is never called. When I tried to debug this in Visual Studio, the execution actually stepped right into the RespCallback part and the lblResult.Text watch actually shows '123123' but the resulting Web page always shows only '12312'
I am sure I am missing something basic here, but I just don't know what. I was even guessing that it has to do with browser cache (hence the result changes for the second time) but I don't know how to fix that either.
Can someone please help? Thanks in advance.
Jim
Dim myWebRequest As WebRequest
Public Shared allDone As New ManualResetEvent(False)
Private Shared BUFFER_SIZE As Integer = 1024
Public Class RequestState
' This class stores the state of the request
Private Shared BUFFER_SIZE As Integer = 1024
Public requestData As StringBuilder
Public bufferRead() As Byte
Public request As WebRequest
Public response As WebResponse
Public responseStream As Stream
Public Sub New()
bufferRead = New Byte(BUFFER_SIZE) {}
requestData = New StringBuilder("")
request = Nothing
responseStream = Nothing
End Sub ' New
End Class ' RequestState
Public Sub GetData(Sender As Object, Args As System.EventArgs)
lblResult.Text += "1"
myWebRequest = WebRequest.Create(dataURL)
Dim myRequestState As New RequestState()
myRequestState.request = myWebRequest
' Start the asynchronous request.
Dim asyncResult As IAsyncResult = CType(myWebRequest.BeginGetResponse(AddressOf RespCallback, myRequestState), IAsyncResult)
lblResult.Text += "2"
allDone.WaitOne()
End Sub
Private Sub RespCallback(asynchronousResult As IAsyncResult)
lblResult.Text += "3"
allDone.Set()
End Sub
I don't know VB so it's hard to read for me but I'm suspecting GetData is your onClick handler.
First thing that is not right is that you have Shared members. Why your reset event is Shared? It makes all requests use the same object.
Basically Your code with ManualResetEvent won't work because after first allDone.Set(), your object remains set (as long as web application lives). To get "123" every time you should add allDone.Reset() after allDone.WaitOne().
In Your situation web request returns to client before RespCallback is called every time except first call (when your reset event is in non-signaled state).
AutoResetEvent resets automatically. That's why it worked.
But! You can't do this this way. Making your ResetEvent Shared you make all request use the same object. When more than one request will be processed by your application at the same time you will get undetermined behavior.
Remove Shared from your code. Than your code will work (but not asynchronously) without allDone.Reset() and without AutoResetEvent. But it will provide known results (not depending on amount of requests).
About asynchronous call (now that we have code "working"). Well. There is no async request to your web page. allDone.WaitOne() waits until your async webRequest finish. So basically you could just as well do synchronous request.
You need a special pattern for asynchronous web pages. You can read how to do this here.
But i'm not sure it's what you wanted. Do you want your request to be called asynchronously so that it will not use server resources or do you want to display some message to the user (like "Data is being downloaded...") while your web page will remain fully responsible?
If it's the second one you should use AJAX functionality (Like UpdatePanel or using JavaScript directly). You can read about it here.
Couple things to check:
If your label is a fixed width, then it's possible the text is being clipped
If you are using an UpdatePanel, you will need to set its mode to 'Conditional' and call Update() on it in the RespCallback callback method so that the UI gets refreshed with the latest label text value.
is there a way in asp.net to make sure that a certain threaded sub is not run twice concurrently, no matter what?
the code i have now is
Public Class CheckClass
ReadOnly Property CheckSessionsLock As Object
Get
If HttpRuntime.Cache("CheckSessionsLock") Is Nothing Then HttpRuntime.Cache("CheckSessionsLock") = New Object
Return HttpRuntime.Cache("CheckSessionsLock")
End Get
End Property
Sub TryThreads()
Dim thread = New Thread(AddressOf TryLock)
thread.Priority = ThreadPriority.Lowest
thread.Start()
End Sub
Sub TryLock()
SyncLock CheckSessionsLock
DoTrace("entered locker")
For x = 0 To 10000
Next
DoTrace("exiting locker")
End SyncLock
DoTrace("exited locker")
End Sub
End Class
if i run this code on every page then several times the code overlaps. the DoTrace function in the code simply writes the message to a table.
the messages in the table should appear in order (entered,exiting,exited) again and again, but in reality, they don't. i get like entered, exiting,entered,exited,exiting...
this means that the synclock is not complete. is that true?
if so, how can we implement a complete synclock on a block of code, across requests and across sessions?
EDIT: i need this lock, as the real code will be sending emails, according to a list of mailing types in a db. after each mailing type is sent, its marked, then it continues with the next mailing. i cant have in middle of processing, another thread should see this mailing as unprocessed.
please advise
Rather than using the HttpRuntime Cache have you considered using a static variable?
Just as a note (it might be helpful to explain why you want this functionality) your website is not going to be very scalable if this can only be run once at a time.
In C# (sorry, don't know VB syntax) I use this:
private static readonly object Padlock = new object();
It's a field, not a property,
It's static (in VB, that's "shared" if I'm not mistaken) so it's the same throughout the entire application
It's initialised once as soon as you use this class, not when you explicitly use the field.
With your property/cache version, you could have two threads trying to get the lock-object and each creating a different one:
Thread 1 checks the cache and doesn't find the object
Thread 1 is parked
Thread 2 checks the cache, doesn't find the object
Thread 2 creates the object and caches it, retrieves it again and returns from the property
Thread 1 resumes
Thread 1 creates a new object and caches it, retrieves it again and returns a different lock object than thread 2 uses
Any further threads will use the lock object of thread 1
''' <summary>
''' Returns true if a submission by the same IP address has not been submitted in the past n minutes.
'' </summary>
Protected Function EnforceMinTimeBetweenSubmissions(ByVal minTimeBetweenRequestsMinutes as Integer) As Boolean
If minTimeBetweenRequestsMinutes = 0 Then
Return True
End If
If Cache("submitted-requests") Is Nothing Then
Cache("submitted-requests") = New Dictionary(Of String, Date)
End If
' Remove old requests. '
Dim submittedRequests As Dictionary(Of String, Date) = CType(Cache("submitted-requests"), Dictionary(Of String, Date))
Dim itemsToRemove = submittedRequests.Where(Function(s) s.Value < Now).Select(Function(s) s.Key).ToList
For Each key As String In itemsToRemove
submittedRequests.Remove(key)
Next
If submittedRequests.ContainsKey(Request.UserHostAddress) Then
' User has submitted a request in the past n minutes. '
Return False
Else
submittedRequests.Add(Request.UserHostAddress, Now.AddMinutes(minTimeBetweenRequestsMinutes))
End If
Return True
End Function
No. The ASP.NET Cache is not inherently thread-safe and it looks like you are creating objects in the Cache depending on whether they exist or not.
You need to lock the Cache when writing to it.
Let me word things a little differently. The code is, in fact, thread safe. The way you currently have it coded though could cause performance issues in multi-threaded situations.
In this case, multiple users would be running the same code simultaneously, theoretically accessing and modifying the same cache objects at the same time. As that scenario scales up, performance suffers.
Creating a lock will improve performance under heavy load (while imposing a slight overhead under light load) because you won't be fetching data neadlessly due to Caching issues.
The System.Web.Caching.Cache class is thread-safe according to the MSDN documenation. However, the documenation also shows an example where a read and a write are performed on the cache without locking. That cannot possibily be thread-safe since the write is dependent on the read. The code you posted basically looks like the example. I definitely recommend putting a lock around the entire method.