Re: Threading.Monitor.Enter that doesn't /quite/ block the thread

Tech-Archive recommends: Repair Windows Errors & Optimize Windows Performance



Nothing jumps out at you saying "OMG!! WHAT'S THIS MAN DOING??!?!?! That's TOTALLY the wrong way to do that!!!" then? :o)

Daniel Moth wrote:
How does this look?
Well only you can test it so if it works as you want it to... that's fine!

Cheers
Daniel
--
http://www.danielmoth.com/Blog/

"Jon Brunson" <jon.brunson@innovation-NOSPAM-software-DOT-co-DOT-uk> wrote
in message news:%23jyOVaiWGHA.1084@xxxxxxxxxxxxxxxxxxxxxxx
Having taken your advice on-board (thank-you so VERY much), now seem to
have a working program, with no dead-locks!

How does this look?

[VB.NET]
Imports System.Data.SqlServerCe

Public Class Form1
Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
MyBase.Dispose(disposing)
End Sub

'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
Friend WithEvents Button1 As System.Windows.Forms.Button
Friend WithEvents ProgressBar1 As System.Windows.Forms.ProgressBar
Friend WithEvents Label1 As System.Windows.Forms.Label
Private Sub InitializeComponent()
Me.ListBox1 = New System.Windows.Forms.ListBox
Me.Button1 = New System.Windows.Forms.Button
Me.ProgressBar1 = New System.Windows.Forms.ProgressBar
Me.Label1 = New System.Windows.Forms.Label
'
'ListBox1
'
Me.ListBox1.Location = New System.Drawing.Point(8, 8)
Me.ListBox1.Size = New System.Drawing.Size(168, 93)
'
'Button1
'
Me.Button1.Location = New System.Drawing.Point(104, 128)
Me.Button1.Text = "Sync Now"
'
'ProgressBar1
'
Me.ProgressBar1.Location = New System.Drawing.Point(8, 108)
Me.ProgressBar1.Size = New System.Drawing.Size(168, 12)
'
'Label1
'
Me.Label1.Font = New System.Drawing.Font("Tahoma", 9.0!,
System.Drawing.FontStyle.Bold)
Me.Label1.Location = New System.Drawing.Point(40, 32)
Me.Label1.Size = New System.Drawing.Size(104, 32)
Me.Label1.Text = "Populating, Please Wait"
Me.Label1.TextAlign = System.Drawing.ContentAlignment.TopCenter
Me.Label1.Visible = False
'
'Form1
'
Me.ClientSize = New System.Drawing.Size(186, 159)
Me.Controls.Add(Me.Label1)
Me.Controls.Add(Me.ProgressBar1)
Me.Controls.Add(Me.Button1)
Me.Controls.Add(Me.ListBox1)
Me.Font = New System.Drawing.Font("Tahoma", 8.25!,
System.Drawing.FontStyle.Regular)
Me.Text = "Form1"

End Sub

Public Shared Sub Main()
Application.Run(New Form1)
End Sub

#End Region

Public Sub New()
MyBase.New()
InitializeComponent()
bgWorker.WorkerReportsProgress = True
' I have no idea what AliceBlue looks like, I only have a monochrome
device to hand!
Label1.BackColor = Color.AliceBlue
End Sub

Private Const FILENAME As String = "\Program Files\MyApp\MyData.sdf"
Private Const MAXVALUE As Integer = 1000
Private WithEvents bgWorker As New
OpenNETCF.ComponentModel.BackgroundWorker
Private ConnectionLocker As New Object
Private bReporting As Boolean

Public ReadOnly Property Connection() As SqlCeConnection
Get
SyncLock ConnectionLocker

Static cnn As SqlCeConnection

If cnn Is Nothing Then _
cnn = New SqlCeConnection

If cnn.ConnectionString Is Nothing OrElse
cnn.ConnectionString.Trim().Length = 0 Then _
cnn.ConnectionString = "Data Source = '" + FILENAME + "'"

If Not IO.File.Exists(FILENAME) Then
Dim e As SqlCeEngine
Dim c As SqlCeCommand
Try
e = New SqlCeEngine(cnn.ConnectionString)
e.CreateDatabase()

cnn.Open()

c = New SqlCeCommand("CREATE TABLE SomeTable (ID int IDENTITY,
Description nvarchar(255))", cnn)
c.ExecuteNonQuery()

Finally
If Not (c Is Nothing) Then c.Dispose()
If Not (e Is Nothing) Then e.Dispose()
End Try
End If

If cnn.State <> ConnectionState.Open Then _
cnn.Open()

Return cnn

End SyncLock
End Get
End Property

Private Sub Form1_Activated(ByVal sender As Object, ByVal e As
System.EventArgs) Handles MyBase.Activated
Try
Me.Label1.Visible = True
Application.DoEvents()
Me.ListBox1.DataSource = Nothing
Me.ListBox1.ValueMember = "ID"
Me.ListBox1.DisplayMember = "Description"
Me.ListBox1.DataSource = GetSomeData()
Catch ex As Exception
MessageBox.Show(ex.Message)
Finally
Me.Label1.Visible = False
End Try
End Sub

Public Function GetSomeData() As DataTable
Dim da As SqlCeDataAdapter
Try
SyncLock ConnectionLocker

da = New SqlCeDataAdapter("SELECT ID, Description FROM SomeTable",
Connection)
Dim rtn As New DataTable
da.Fill(rtn)

Return rtn

End SyncLock
Finally
If Not (da Is Nothing) Then da.Dispose()
End Try
End Function

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
bgWorker.RunWorkerAsync()
End Sub

Private Sub bgWorker_DoWork(ByVal sender As Object, ByVal e As
OpenNETCF.ComponentModel.DoWorkEventArgs) Handles bgWorker.DoWork
Dim cmd As SqlCeCommand
bReporting = False
Try
SyncLock ConnectionLocker

cmd = New SqlCeCommand("INSERT INTO SomeTable (Description)
VALUES(?)", Connection)
cmd.Parameters.Add("@Description", SqlDbType.NVarChar)
For i As Integer = 1 To MAXVALUE
cmd.Parameters(0).Value = i
cmd.ExecuteNonQuery()
If Not bReporting Then
bReporting = True
bgWorker.ReportProgress(CInt((i / MAXVALUE) * 100), i)
End If
Next

End SyncLock
Catch ex As Exception
MessageBox.Show(ex.Message)
Finally
If Not (cmd Is Nothing) Then cmd.Dispose()
End Try
End Sub

Private Sub bgWorker_ProgressChanged(ByVal sender As Object, ByVal e As
OpenNETCF.ComponentModel.ProgressChangedEventArgs) Handles
bgWorker.ProgressChanged
Me.ProgressBar1.Value = e.ProgressPercentage
Application.DoEvents()
bReporting = False
End Sub

Private Sub bgWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e
As OpenNETCF.ComponentModel.RunWorkerCompletedEventArgs) Handles
bgWorker.RunWorkerCompleted
Me.ProgressBar1.Value = Me.ProgressBar1.Maximum
Application.DoEvents()
End Sub

End Class
[/VB.NET]

Daniel Moth wrote:
I hope my replies to your other posts (in this and other threads) give
you something to work at.

Just some quick comments on the code you posted:
1. The area with button1_click, syncnow, updateprogressbar is a classic
fit for using the BackgroundWorker instead. Do check that out.
2. If all you are doing in a Catch is Throw, then you might as well omit
the catch block.
3. Rather than explicit use of Monitor.Enter/Exit, do use SyncLock as it
will make your code clearer to read
4. If your UI needs to fetch some data (e.g. your activated method) and
the data cannot be read right now (e.g. your db is busy being used by
something else) do not block the UI. E.g. put a friendly message to the
user that allows them to cancel or retry (if you don't want to do that
automatically). At the moment you are kind of assuming that the data will
be returned no matter what.
5. Generally don't lock on properties and specifically don't lock on the
connection object. Use a dedicated object for this purpose (object obj =
new object())

Other than that, using the EventWaitHandle (WaitOne with timeout) as I
suggested is your best bet here.

Cheers
Daniel
--
http://www.danielmoth.com/Blog/

"Jon Brunson" <jon.brunson@innovation-NOSPAM-software-DOT-co-DOT-uk>
wrote in message news:ugYthrYWGHA.4484@xxxxxxxxxxxxxxxxxxxxxxx
Jon Brunson wrote:
Daniel Moth wrote:
If you can post a small sample demonstrating the issue/need then we
can discuss it over something concrete.

' Assume MyData.sdf exists, and contains a table called "SomeTable",
' which has two columns: ID int AUTO_INCREMENT,
' and Description nvarchar(255)

Private Shared __Connection As SqlCeConnection
Friend Shared Property _Connection() As SqlCeConnection
Get
If __Connection Is Nothing Then _
__Connection = New SqlCeConnection
Return __Connection
End Get
Set(ByVal Value As SqlCeConnection)
If Value Is Nothing Then _
Value = New SqlCeConnection
__Connection = Value
End Set
End Property
Public ReadOnly Property Connection() As SqlCeConnection
Get
Try
Threading.Monitor.Enter(_Connection)

If _Connection.ConnectionString Is Nothing OrElse
_Connection.ConnectionString.Trim().Length = 0 Then
_Connection.ConnectionString = "Data Source = 'MyData.sdf'"
End If

If _Connection.State <> ConnectionState.Open Then
_Connection.Open()
End If

Return _Connection

Catch
Throw
Finally
Threading.Monitor.Exit(_Connection)
End Try
End Get
End Property

Private Sub Form1_Activated(ByVal sender As Object, ByVal e As
System.EventArgs) Handles MyBase.Activated
Try
Me.ListBox1.DataSource = GetSomeData()
Me.ListBox1.ValueMember = "ID"
Me.ListBox1.DisplayMember = "Description"
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub

Public Function GetSomeData() As DataTable
Dim da As SqlCeDataAdapter
Try
Threading.Monitor.Enter(_Connection)

da = New SqlCeDataAdapter("SELECT ID, Description FROM SomeTable",
Connection)
Dim rtn As New DataTable
da.Fill(rtn)

Return rtn

Catch
Throw
Finally
If Not (da Is Nothing) Then da.Dispose()
Threading.Monitor.Exit(_Connection)
End Try
End Function

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Dim t As New Threading.Thread(AddressOf SyncNow)
t.Start()
End Sub

Private Const MaxValue As Integer = 100000
Private CurrentValue As Integer

Public Sub SyncNow()
Dim cmd As SqlCeCommand
Try
Threading.Monitor.Enter(_Connection)

cmd = New SqlCeCommand("INSERT INTO SomeTable (Description)
VALUES(?)", Connection)
cmd.Parameters.Add("@Description", SqlDbType.NVarChar)
For i As Integer = 1 To MaxValue
cmd.Parameters(0).Value = i
cmd.ExecuteNonQuery()
CurrentValue = i
Me.Invoke(New EventHandler(AddressOf UpdateProgressBar))
Next

Catch ex As Exception
MessageBox.Show(ex.Message)
Finally
Threading.Monitor.Exit(_Connection)
If Not (cmd Is Nothing) Then cmd.Dispose()
End Try
End Sub

Public Sub UpdateProgressBar(ByVal sender As Object, ByVal e As
EventArgs)
Me.barTotal.Value = CInt((CurrentValue / MaxValue) * 100)
End Sub


Full code available at: http://www.tfxsoft.com/vitani/Form1.vb


.



Relevant Pages

  • Re: Threading.Monitor.Enter that doesnt /quite/ block the thread
    ... Protected Overloads Overrides Sub Dispose ... Public ReadOnly Property Connection() As SqlCeConnection ... Private Sub Form1_Activated(ByVal sender As Object, ...
    (microsoft.public.dotnet.framework.compactframework)
  • Re: Threading.Monitor.Enter that doesnt /quite/ block the thread
    ... Protected Overloads Overrides Sub Dispose ... Private Sub InitializeComponent() ... Public ReadOnly Property ConnectionAs SqlCeConnection ... Generally don't lock on properties and specifically don't lock on the connection object. ...
    (microsoft.public.dotnet.framework.compactframework)
  • Re: Threading.Monitor.Enter that doesnt /quite/ block the thread
    ... Private Shared __Connection As SqlCeConnection ... Friend Shared Property _ConnectionAs SqlCeConnection ... Private Sub Form1_Activated(ByVal sender As Object, ...
    (microsoft.public.dotnet.framework.compactframework)
  • Re: Connection problem
    ... There's a lot of debate whether you actually need to close the connection ... Doug Steele, Microsoft Access MVP ... the Form_Load sub, they're only known inside that sub. ... Private Sub Form_Load ...
    (microsoft.public.access.formscoding)
  • Re: Threading.Monitor.Enter that doesnt /quite/ block the thread
    ... Private Shared __Connection As SqlCeConnection ... Friend Shared Property _Connection() As SqlCeConnection ... Private Sub Form1_ActivatedHandles MyBase.Activated ...
    (microsoft.public.dotnet.framework.compactframework)