asp.net multithreading with synclock - asp.net

i have some test code which i run at every load of a page in my asp.net website
this is the code
Sub TryThreads()
Dim t1 = New Thread(AddressOf TryLock)
t1.Priority = ThreadPriority.Lowest
t1.Start()
Dim t2 = New Thread(AddressOf TryLock)
t2.Priority = ThreadPriority.Lowest
t2.Start()
End Sub
Sub TryLock()
Dim LockObject = New Object
SyncLock LockObject
DoTrace("entered locker")
For x = 0 To 10000
Next
DoTrace("exiting locker")
End SyncLock
DoTrace("exited locker")
End Sub
the "dotrace" simply add a record to a log table in the db
now the right result would be that i should have the entries in the db in order "entered","exiting","exited"
but actually when i look in the db i see first 2 "entered" then 2 "exiting" etc.
meaning that the multithreading is working ok, but not the synclock
is that correct?
and how can this be fixed?
the real code will be adding records to the db and might be called from several pages of different sessions, but the same code must not run twice concurrently
i do appreciate anybodys help
thank you very much!!!
EDIT:
in response to Sashas wonderful post i changed my code to a class (it was in a module) and now it looks like this:
Public Class CheckClass
Property LockObject As Object
Get
If HttpRuntime.Cache("CheckSessionsLock") Is Nothing Then HttpRuntime.Cache("CheckSessionsLock") = New Object
Return HttpRuntime.Cache("CheckSessionsLock")
End Get
Set(ByVal value As Object)
If value Is Nothing Then
HttpRuntime.Cache.Remove("CheckSessionsLock")
Else
HttpRuntime.Cache("CheckSessionsLock") = value
End If
End Set
End Property
Sub TryThreads()
Dim t1 = New Thread(AddressOf TryLock)
t1.Priority = ThreadPriority.Lowest
t1.Start()
Dim t2 = New Thread(AddressOf TryLock)
t2.Priority = ThreadPriority.Lowest
t2.Start()
End Sub
Sub TryLock()
SyncLock LockObject
DoTrace("entered locker")
For x = 0 To 10000
Next
DoTrace("exiting locker")
End SyncLock
DoTrace("exited locker")
End Sub
End Class
now it works 80-90% of the time.
on page load i have:
Dim cc = New CheckClass
cc.TryThreads()
if i open multiple pages at once, they still clash some times. but if i'm correct, the issue is now not with the synclock as much as with the httpruntime.cache, because when using a standard property, on one page, the code works 100%.
so how can i make sure that 2 threads, even from totally different sessions never run the trylock simultaneously?
thank you all for helping out

You are creating a new object instance when the TryLock method is called, and use that for locking. If you want mutual exclusion between the two threads, you need to use a common object instance for locking, e.g. a static member of your class or a parameter that you pass to both threads.

Related

How can I store the data in memory and use by the other Button click event to display the data?

Here is the code, but the datatable is NULL in ButtonExport click event, how can i pass the DataTable to Sub ButtonExport_Click ? I dont want to store in Session as the data is too big
Here is the class clsGlobalVarriable
Public Class clsGlobalVariable
Private _gdt As DataTable
Public Property globalDataTable As DataTable
Get
Return _gdt
End Get
Set(ByVal value As DataTable)
_gdt = value
End Set
End Property
End Class
Here is the From frmTest code:
Public Class frmTest
Inherits System.Web.UI.Page
Private gdt As New clsGlobalVariable
Protected Sub ButtonInactivePC_Click(sender As Object, e As EventArgs) Handles ButtonInactivePC.Click
Try
Dim func As New clsFunction
Dim command As String = "Get-ADComputer -Filter { OperatingSystem -NotLike '*Windows Server*'} -Property * | select Name, CanonicalName, operatingSystem, LastLogonDate, Description, whenChanged | Where {($_.LastLogonDate -lt (Get-Date).AddDays(-90)) -and ($_.LastLogonDate -ne $NULL)}"
Dim arr As New ArrayList
arr.Add("Name")
arr.Add("CanonicalName")
arr.Add("operatingSystem")
arr.Add("LastLogonDate")
arr.Add("whenChanged")
arr.Add("Description")
gdt.globalDataTable = func.PSObjectToDataTable(command, arr)
Me.GridView1.DataSource = gdt.globalDataTable
Me.GridView1.DataBind()
Catch ex As Exception
Me.LabelDebug.Text = "Button Click" + ex.Message
End Try
End Sub
Protected Sub ButtonExport_Click(sender As Object, e As EventArgs) Handles ButtonExport.Click
Dim func As New clsFunction
Dim dt As New DataTable
dt = (DirectCast(Me.GridView1.DataSource, DataTable))
Me.LabelDebug.Text = "Global Data Table Count = " & dt.Rows.Count
End Sub
When working with webpages that show data to the user, and the user takes some action on that data you either need to store the data somewhere in their computer, your computer (the server) or rely on the fact that it's still stored in the computer you got it from. As a process you have undertaken:
You generate a grid from querying AD
You send the grid to the customer's computer - so it's stored there as a visual representation (and maybe also ViewState)
It's still stored in AD, where you got it
You could also store it locally on the server somehow - Session, DB, text file, whatever
Decide on which of these to use when the user clicks Export:
Dig it out of the viewstate or other data that was sent to the user - for this you'll have to code things up so it comes back from the user
Get it out of AD again - simple to do; you did it once and sent it to the user in HTML. Getting it again and sending it to the user again this time as a CSV isn't really any different from the first time you did it
Restore it from wherever you kept it on the server
Choose the first if your user is going to modify the data or choose to export only some of it - the data he sends back to you should indicate which bits he wants exporting.
Choose the second option if you want an easy life, and it's just a straight export, no editing or subset of data. Write one method that gets the data out of AD and then use it in either place, one to form HTML/fill a grid, in the other to send a file to the user. Don't get hung up on "well I already got this data once, it's a waste to get it again" - no-one writes a Login Page and thinks "i'll only ever look up a user from the DB once, then get the server to remember the login data forever more and use it next time there is a login request" - they store the data in the db, and look it up every time there is a login. DBs store data and perform the same queries over and over again. This is no different
You probably wouldn't choose the third option, for reasons already mentioned
I decided to use alternative for the Excel Export, i am not going to pass the DataTable, instead i pass the GridView to the Export to Excel function
Add the following sub right after Page_load, this is to avoid the GridView error
Public Overrides Sub VerifyRenderingInServerForm(ByVal control As Control)
End Sub
Here is the Code:
Public Sub ExportFromGridview(ByVal gv As GridView, ByVal response As HttpResponse
response.Clear()
response.Write("<meta http-equiv=Content-Type content=text/html;charset=utf-8>")
response.AddHeader("content-disposition", "attachment;filename=" & Now & ".xls")
response.ContentType = "application/vnd.xls"
Dim stringWrite As System.IO.StringWriter = New System.IO.StringWriter()
Dim htmlWrite As System.Web.UI.HtmlTextWriter = New HtmlTextWriter(stringWrite)
gv.RenderControl(htmlWrite)
response.Write(stringWrite.ToString())
response.End()
End Sub

Is assigning an object to itself a good idea?

I have two classes, RecordSet and Record. RecordSet has a Generic List(Of Record).
I can add objects to the list by calling my RecordSet.AddRecord(ObjRecord) function, which returns RecordSet. When the list has a count of 200, some processing occurs and a new RecordSet object is returned, otherwise itself is returned and the application can carry on adding Record objects to the list.
My concern is that there will be 200 objects of RecordSet until garbage collection does it's sweep. Is this a good idea?
Public Class RecordSet
Private lstRecords As New List(Of Record)
Public Function AddRecord(SomeVariable) AS RecordSet
lstRecords.Add(New Record())
If lstRecords.Count = 200 Then
Me.ProcessTheRecords()
Return New RecordSet()
Else
Return Me
End If
End Function
Private Sub ProcessTheRecords()
'Do stuff in here
End Sub
Private Class Record
Public Sub New()
End Sub
End Class
End Class
Then in my application I call:
Dim objRecordSet AS New RecordSet
For Each VariableName In SomeList
objRecordSet = objRecordSet.AddRecord(VariableName)
Next
'Process the remaining objects in objRecordSet here.
First of all, this is really bad pratice, it's hard to follow the code for someone new and is a potential bug source. Instead of returning urself every time, change your design.
Change your function to this:
Public Sub AddRecord(SomeVariable)
lstRecords.Add(New Record()) <--- should't you be doing something with SomeVariable?!
If lstRecords.Count = 200 Then
Me.ProcessTheRecords()
end if
End Function
Private Sub ProcessTheRecords()
'Do stuff in here
Me.lstRecords.clear()
End Sub
Now AddRecord does exactly what it says it does - it adds a new record and modifies the recordSet. ProcessTheRecords does the processing, as its supposed to do, and if u need to clear the list container - oh well, just clear it.
I strongly recommed to read this wiki article about
Cohesion.
Just as a proposiontion, the AddRecord could be a function of return type Boolean, which indicates the success of the operation (maybe an error or exception can be raised by the processing function?).
It's much cleaner now, isn't it?

How to force linq to Sql to execute query straight away

I have a repeater of controls on an aspx page. I am trying to move a query out of the control and into the parent page as there is a massive hit on the DB for each control. The problem is that I get the following error.
Cannot access a disposed object.
Object name: 'DataContext accessed after Dispose.'.
I don't understand this as I thought that doing .ToList() forces the query to be executed
My code in the Parent page
Private _activityList As IEnumerable(Of Activity)
Public ReadOnly Property ActivityList() As IEnumerable(Of Activity)
Get
Return _activityList
End Get
End Property
Sub PopulatePage()
Dim activityList = From a In dbContext.Activities Where a.PA.PA_Key.ToUpper().Trim() = "DCC" _
Select a
_activityList = activityList.AsEnumerable()
End Sub
The code in my control is:
Public _activityList As List(Of Activity)
Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
_activityList = CType(Me.Page, ParentPage).ActivityList.ToList()
End Sub
Sub grdSelectedActivities_NeedDataSource(ByVal source As Object, ByVal e As Telerik.WebControls.GridNeedDataSourceEventArgs) Handles grdSelectedActivities.NeedDataSource
Dim lnqActivities = _activityList
Dim objActivity As Activity = (From x In lnqActivities Where x.AC_Code = ActivityCode Select x).Single
Dim lnqRoundActivities = (From roundactivity In objActivity.RoundActivities Where roundactivity.RA_DS_Code = DepartmentalSettingsCode
Select roundactivity Order By roundactivity.RA_Name)
grdSelectedActivities.DataSource = lnqRoundActivities
End Sub
EDIT
I think that is is failing as it is trying to get RoundActivities in the grdSelectedActivities_NeedDataSource control method. Therefore I need to send an Activities object which has RoundActivityies child objects.
I have tried to create this object but get the following error:
Explicit construction of entity type 'Activity' in query is not allowed.
enter code here
This is the updated code:
Dim activityList = (From a In dbContext.Activities Where a.PA.PA_Key.ToUpper().Trim() = "DCC" Select New Activity With {.AC_Code = a.AC_Code, .RoundActivities = a.RoundActivities})
Solution:
I followed #kristoferA advice and did the following
Dim loadOptions = New DataLoadOptions()
loadOptions.LoadWith(Of Activity)(Function(a As Activity) a.RoundActivities)
loadOptions.AssociateWith(Of Activity)(Function(a As Activity) a.RoundActivities.Where(Function(z) If(z.RA_DS_Code = departmentCode, False)))
dataContext.LoadOptions = loadOptions
Sounds like you are lazy loading some association.
Turn off lazy loading (dc.DeferredLoadingEnabled = false), and pass a DataLoadOptions object to dc.LoadOptions to instruct the DC what you want to eager load.
ai.farfa is correct with the ToList(). That is the call that trigger the actual query.
I think you should do it on this line though :
grdSelectedActivities.DataSource = lnqRoundActivities.ToList()
I guest that the dbContext get disposed after PopulatePage() done and your Linq select return type is IEnumerable(Of Activity) which's just a prepared SQL statement.
try..
_activityList = activityList.ToList()//.AsEnumerable()
Edit
if your Model is stateless you can create new dbContext then enumerate and dispose it.
Using db As New dbContext()
Dim activityList = (From a In dbContext.Activities Where a.PA.PA_Key.ToUpper().Trim() = "DCC" _
Select a).ToList()
_activityList = activityList.ToList()
End Using

Same Function Running Parallel will override values?

I am Using same function from many places...
for example below function
Public Sub getUser(ByVal Name as string)
dim myName=Name
.......
insert(myName)
End Sub
I am using this function from so many places...
I have doubt should this function override this myName values with latest function call?
Suppose i called getUser("ABC") so value of myName is now ABC now sudden all call getUser("XYZ") so at insert(myName) will it insert("ABC") or insert("XYZ")??
I need it to be insert("ABC") and then insert("XYZ")
You can use locking to make sure only one thread does something at a time
//declare an object for locking
Dim lockObjcect As New [Object]()
Public Sub getUser(ByVal Name as string)
SyncLock lockObjcect
dim myName=Name
.......
insert(myName)
End SyncLock
End Sub
With the locking, now only one thread will be able to execute the code between SyncLock and End SyncLock this means First ABC will be inserted and then XYZ will be inserted

How to add Transactions with a DataSet created using the Add Connection Wizard?

I have a DataSet that I have added to my project where I can Insert and Add records using the Add Query function in Visual Studio 2010, however I want to add transactions to this, I have found a few examples but cannot seem to find one that works with these.
I know I need to use the SQLClient.SQLTransaction Class somehow. I used the Add New Data Source Wizard and added the Tables/View/Functions I need, I just need an example using this process such as How to get the DataConnection my DataSet has used. Assuming all options have been set in the wizard and I am only using the pre-defined adapters and options asked for in this wizard, how to I add the Transaction logic to my Database.
For example I have a DataSet called ProductDataSet with the XSD created for this, I have then added my Stock table as a Datasource and Added an AddStock method with a wizard, this also if a new item calls an AddItem method, if either of these fails I want to rollback the AddItem and AddStock in this case.
In this example, I have a dataset called "dsMain" and a few direct queries in a "QueriesTableAdapter". I extend the partial class for the TableAdapter with a function that will create a transaction based on the first (0) connection and then apply it to every connection in the table adapter.
Namespace dsMainTableAdapters
Partial Public Class QueriesTableAdapter
Public Function CreateTransaction() As Data.IDbTransaction
Dim oConnection = Me.CommandCollection(0).Connection
oConnection.Open()
Dim oTrans = oConnection.BeginTransaction()
For Each cmd In Me.CommandCollection
cmd.Connection = oConnection
cmd.Transaction = oTrans
Next
Return oTrans
End Function
End Class
End Namespace
You begin the transaction by calling the new function
Dim qa As New dsMainTableAdapters.QueriesTableAdapter
Dim oTrans = qa.CreateTransaction()
Then you can call TableAdapter queries within your transaction
qa.Query1
qa.Query2
When you are done with your queries you commit the transaction
oTrans.Commit()
You can do the same thing for any TableAdapter that was created for your datasets.
If you have multiple TableAdapters that need to use the same transaction, then in addition to a "CreateTransaction" you should make a "SetTransaction" and have the Transaction be a parameter.
first of all thanks for your answer carter, it helped me very much!
but iam not able to handle the part with the parameters
You can do the same thing for any TableAdapter that was created for your datasets. If you have multiple TableAdapters that need to use the same transaction, then in addition to a "CreateTransaction" you should make a "SetTransaction" and have the Transaction be a parameter.
so iam able to handle 1 transactions with 1 tableadapter, but not 1 transaction with 2 tableadapters:
iam doing this for a school project, and i really need your help!!
here is the code to add a new material and a historical price to it(a changing price, like by fuel; iam saving it in an related table to material in the database):
Namespace DataSetTableAdapters
Partial Public Class MaterialPriceTableAdapter
Public Function SetTransaction() As Data.IDbTransaction
Dim oConnection = Me.CommandCollection(0).Connection
oConnection.Open()
Dim oTrans = oConnection.BeginTransaction()
For Each cmd In Me.CommandCollection
cmd.Connection = oConnection
cmd.Transaction = oTrans
Next
Return oTrans
End Function
End Class
Partial Public Class MaterialTableAdapter
Public Function CreateTransaction(ByVal MaterialPrice As System.Data.Odbc.OdbcTransaction) As Data.IDbTransaction
Dim oConnection = Me.CommandCollection(0).Connection
oConnection.Open()
Dim oTrans = oConnection.BeginTransaction()
For Each cmd In Me.CommandCollection
cmd.Connection = oConnection
cmd.Transaction = oTrans
Next
Return oTrans
End Function
End Namspace
`
and now the code in the form the form:
Public Class AddMaterial
Dim material As New DataSetBATableAdapters.MaterialTableAdapter
Dim materialprice As New DataSetBATableAdapters.MaterialPriceTableAdapter
Dim oTrans = material.CreateTransaction(materialprice.SetTransaction())
Private Sub Save_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Save.Click
Try
material.InsertQuery(NameTextBox.Text, UnitComboBox.SelectedValue)
materialprice.InsertQuery(Date_BeginDateTimePicker.Value, PriceTextBox.Text, Date_EndDateTimePicker.Value, Me.LkwTableAdapter.ScalarQuery())
oTrans.Commit()
Catch ex As Exception
oTrans.Rollback()
MsgBox("Error by Insert")
End Try
Me.Close
End Sub
End Class
if i save a new record the materialprice.insertquery isnt commited by otrans.commit. what am i doing wrong? if you have an idea what it is, please tell me
thanks,
Xeras
This is untested, but this is how I imaging the CreateTransaction/SetTransaction combo should be written (with your OdbcTransaction object).
Public Function CreateTransaction() As System.Data.Odbc.OdbcTransaction
Dim oConnection = Me.CommandCollection(0).Connection
oConnection.Open()
Dim oTrans = oConnection.BeginTransaction()
SetTransaction(oTrans)
Return oTrans
End Function
Public Sub SetTransaction(ByVal oTrans As System.Data.Odbc.OdbcTransaction)
For Each cmd In Me.CommandCollection
cmd.Connection = oTrans.Connection
cmd.Transaction = oTrans
Next
End Sub

Resources