Related
I have a form whose role is to show the user a circular progress graphic while the user is waiting on particular stuff to be done. This is the simple code of it:
Public Class FrmCircularProgress
Sub New(progressType As DevComponents.DotNetBar.eCircularProgressType)
InitializeComponent()
CircularProgress1.ProgressBarType = progressType
StartCircular()
End Sub
Public Sub StartCircular()
Me.CircularProgress1.IsRunning = True
End Sub
Public Sub StopCircular()
Me.CircularProgress1.IsRunning = False
End Sub
End Class
Below is an example of how I use it (in this case two places)
Dim createArticle As New Artikel
'http://stackoverflow.com/questions/33030706/put-long-running-method-into-task-showing-new-form-meantime-and-closing-it-once
Dim pic As New FrmCircularProgress(eCircularProgressType.Donut)
Dim tsk As Task(Of Boolean) = Task.Factory.StartNew(Of Boolean)(Function()
'--Run lenghty task
Dim resu = False
Try
resu = createArticle.ProcessArticle(_artikelsAndTheirVariationsFinal)
'--Close form once done (on GUI thread)
Catch sqlex As Exception
pic.Invoke(Sub() MessageBox.Show(pic, sqlex.Message))
' pic.Invoke(Sub() MessageBox.Show(pic, ex.Message))
'pic.Invoke(Sub() TaskDialog.Show(pic, New TaskDialogInfo("Information", eTaskDialogIcon.BlueStop, "WizardPageDescriptionUberblick_BeforePageDisplayed", ex.ToString, eTaskDialogButton.Ok, eTaskDialogBackgroundColor.Blue, Nothing, Nothing, Nothing, "Jakis footer text", Nothing)))
Finally
End Try
pic.Invoke(New Action(Sub() pic.StopCircular()))
pic.Invoke(New Action(Sub() pic.Close()))
Return resu
End Function)
'--Show the form
pic.ShowDialog()
Task.WaitAll(tsk)
If tsk.Result = True Then
TaskDialog.Show(New TaskDialogInfo("Information", eTaskDialogIcon.BlueStop, "Infor", "New articel and every data has been added correctly", eTaskDialogButton.Ok, eTaskDialogBackgroundColor.Blue, Nothing, Nothing, Nothing, "Jakis footer text", Nothing))
'http://stackoverflow.com/questions/33030706/put-long-running-method-into-task-showing-new-form-meantime-and-closing-it-once
pic = New FrmCircularProgress(eCircularProgressType.Line)
Dim work As Task = Task.Factory.StartNew(Sub()
'--Run lenghty task
PrepareUberblick()
'--Close form once done (on GUI thread)
pic.Invoke(New Action(Sub() pic.StopCircular()))
pic.Invoke(New Action(Sub() pic.Close()))
End Sub)
'--Show the form
pic.ShowDialog()
Task.WaitAll(work)
If WYSWIG_Uberblick.Document IsNot Nothing Then
WYSWIG_Uberblick.Document.Write(String.Empty)
End If
'--Pobranie wszystkich html'ow wszystkich podsekcji artykulow (w tym wypadku numerów artykułów jako podsekcji) (dla sekcji Uberblick)
WYSWIG_Uberblick.DocumentText = _htmlFactory.GetAllUberblickHTML
Else
TaskDialog.Show(New TaskDialogInfo("Information", eTaskDialogIcon.NoEntry, "Infor", "Critical error occured", eTaskDialogButton.Ok, eTaskDialogBackgroundColor.Blue, Nothing, Nothing, Nothing, "Jakis footer text", Nothing))
e.Cancel = True
End If
ProcessArticle function:
Public Function ProcessArticle(artikel As ArticlesVariations) As Boolean
Dim result = True
Dim strcon = New AppSettingsReader().GetValue("ConnectionString", GetType(System.String)).ToString()
Using connection As New SqlConnection(strcon)
'-- Open generall connection for all the queries
connection.Open()
'-- Make the transaction.
Dim transaction As SqlTransaction
transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted)
Dim newArticleRowId As Integer = 0
Dim articleIndex As Integer = 0
Try
For Each kvp As KeyValuePair(Of Integer, Artikel) In artikel.collection
Dim ckey As Integer = kvp.Key
articleIndex = kvp.Key 'save article key
Dim data As Artikel = kvp.Value
'-- If given article contains images list (artikel_images is a list with pictures associated with article)
If Not IsNothing(artikel.collection(articleIndex).ArtikelImages) Then
For Each img In artikel.collection(articleIndex).ArtikelImages
'--Insert article's images if exists
Using cmd As New SqlCommand("INSERT INTO T_Article_Image (Path, FK_Artikel_ID, Position) VALUES (#Path, #FK_Artikel_ID, #Position)", connection)
cmd.CommandType = CommandType.Text
cmd.Connection = connection
cmd.Transaction = transaction
cmd.Parameters.AddWithValue("#Path", img.Path)
cmd.Parameters.AddWithValue("#FK_Artikel_ID", newArticleRowId)
cmd.Parameters.AddWithValue("#Position", img.Position)
cmd.ExecuteScalar()
End Using
Next
End If
'-- If given article contains articles variations list (artikel_variation_attributes is a list with variations associated with article)
If Not IsNothing(artikel.collection(articleIndex)._artikel_variation_attributes) Then
For Each var In artikel.collection(articleIndex)._artikel_variation_attributes
'--Insert article's images if exists
Using cmd As New SqlCommand("INSERT INTO T_Artikel_T_Variation (FK_Variation_VariationAttribute_ID, FK_Artikel_ID, Position) VALUES (#FK_Variation_VariationAttribute_ID, #FK_Artikel_ID, #Position)", connection)
cmd.CommandType = CommandType.Text
cmd.Connection = connection
cmd.Transaction = transaction
cmd.Parameters.AddWithValue("#FK_Variation_VariationAttribute_ID", New Variation_VariationAttribute(var.FkVariationId, var.FkVariationAttributeId).GetId())
cmd.Parameters.AddWithValue("#FK_Artikel_ID", newArticleRowId)
cmd.Parameters.AddWithValue("#Position", var.Position)
cmd.ExecuteScalar()
End Using
Next
End If
Next
transaction.Commit()
Catch ex As Exception
result = False
'-- Roll the transaction back.
Try
transaction.Rollback()
Catch ex2 As Exception
' This catch block will handle any errors that may have occurred
' on the server that would cause the rollback to fail, such as
' a closed connection.
'Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType())
'Console.WriteLine(" Message: {0}", ex2.Message)
End Try
End Try
End Using
Return result
End Function
Everything works correctly, however, when it comes to error or whatever within those methods (from our example):
Dim resu As Boolean = createArticle.ProcessArticle(_artikelsAndTheirVariationsFinal)
or this method:
PrepareUberblick()
My circural form is not closing, but it's still running (it's stuck). When I do Alt+F4 to kill my circular form, I see an error message. I assume that when an error occurs, the error message window is not going to be shown in front but it's hidden behind the circular form.
Here's the question: do you know how to fix it so when an error occurs, an error message show up in front, so user could acknowledge and then the circular form would be closed?
If you want to alert the user that something happened, you could show the MessageBox from your circular form, which should appear on top of it because it's generated on the same thread. You can show the MessageBox in the Catch
Try
'--Run lenghty task
resu = createArticle.ProcessArticle(_artikelsAndTheirVariationsFinal)
Catch ex As Exception
pic.Invoke(Sub() MessageBox.Show(ex.message))
End Try
'--Close form once done (on GUI thread)
pic.Invoke(New Action(Sub() pic.StopCircular()))
pic.Invoke(New Action(Sub() pic.Close()))
This way, the user will need to click OK on the MessageBox before the circular form closes.
Your whole issue stemmed from the fact that you were not calling MessageBox.Show() on the same thread that the form was created on. It's not clear from your example where the form is created, whether it's on the UI thread or not. In either case, whichever thread the form is created on must be the same one that the message box is raised on, in order for the MessageBox to be modal to the form. By calling pic.Invoke(Sub() MessageBox.Show(ex.message)), you ensure it's shown on the form's thread, and will be modal to it. You can typically force the parent window by using the overload which has
Public Shared Function Show (
owner As IWin32Window,
text As String
) As DialogResult
which would be
pic.Invoke(Sub() MessageBox.Show(pic, ex.message))
Also see:
Does MessageBox.Show() automatically marshall to the UI Thread?
Why use a owner window in MessageBox.Show?
I'm trying to implement tasks in my program. I launch a task that will produce a log file, and after, I want to update the label to say "Log sucessfully saved".
Here is my code
Private Function Createlog(ByVal mylist As List(Of classTest))
Dim sw As New StreamWriter("log_list.log")
For index = 1 To mylist.Count - 1
sw.WriteLine(mylist(index).comments)
Next
sw.Close()
Try
Me.Invoke(UpdateLabel("Log sucessfully saved"))
Catch ex As Exception
End Try
Return 1
End Function
Private Function UpdateLabel(ByVal text As String)
Label1.Text = text
Return 1
End Function
I launch the task from the Main form in the Load() :
Dim tasktest = Task(Of Integer).Factory.StartNew(Function() Createlog(theList))
(I don't know if it is better to use the factory or declare as a task and then task.Start())
I have the error on the label update :
Cross-thread operation not valid: Control 'Label1' accessed from a thread
other than the thread it was created on.
Could you please explain why it doesn't work with the invoke method ? And do you have an alternative solution ?
Thanks for your help
First, UpdateLabel should be a Sub, not a Function. Second, this line is wrong:
Me.Invoke(UpdateLabel("Log sucessfully saved"))
Read it again. You are, in order, executing the UpdateLabel function, then passing the result of that function to Me.Invoke (if you used Sub instead of Function, the compiler should have warned you about the error).
This doesn't raise any compiler errors because a Function declared without As [Type] is defaulted to As Object, that can be cast to anything. It should be:
Me.Invoke(Sub()
UpdateLabel("Log sucessfully saved")
End Sub)
To simplify, your code can be rewritten like this:
Private Sub Createlog(ByVal mylist As List(Of classTest))
Dim sw As New StreamWriter("log_list.log")
For index = 1 To mylist.Count - 1
sw.WriteLine(mylist(index).comments)
Next
sw.Close()
Me.Invoke(Sub()
Label1.Text = "Log sucessfully saved"
End Sub)
End Sub
I have datagrid with some data and I'm making a loop through the items with for... next
Inside the loop i'm making a call to a web service with parameters from the datagrid
Because of the speed the work is very time consuming and I want to give the user the option to select how many multiple calls to the service he want.
How can I make simultaneously calls to the web service inside the loop?
There are many ways to achieve what you want so here's an example using the Task class. The key point is to iterate the underlying data source (read only) inside the background thread. When finished, move back to the UI thread and update.
Private Sub BeginAsyncOp(list As IList)
Static cachedSource As CancellationTokenSource
Static cachedId As Long
If ((Not cachedSource Is Nothing) AndAlso (Not cachedSource.IsCancellationRequested)) Then
cachedSource.Cancel()
End If
cachedSource = New CancellationTokenSource
cachedId += 1L
Dim token As CancellationToken = cachedSource.Token
Dim id As Integer = cachedId
Task.Factory.StartNew(
Sub()
Dim result As IList = Nothing
Dim [error] As Exception = Nothing
Dim cancelled As Boolean = False
Try
'Background thread, do not make any UI calls.
For Each item In list
'...
token.ThrowIfCancellationRequested(True)
Next
result = a_updated_list
Catch ex As OperationCanceledException
cancelled = True
Catch ex As ObjectDisposedException
cancelled = True
Catch ex As Exception
[error] = ex
Finally
If (id = cachedId) Then
Me.Invoke(
Sub()
If (((Not Me.IsDisposed) AndAlso (Not Me.Disposing))) Then
'UI thread.
If (Not [error] Is Nothing) Then
'...
ElseIf (Not cancelled) Then
For Each item In result
'...
Next
End If
End If
End Sub)
End If
End Try
End Sub)
End Sub
How do I control access to a shared resource in a Parallel.ForEach loop? I am trying to download multiple files in parallel, and I want to capture information about downloads that fail, so that the user can re-attempt the download later. However, I am worried that if more than one download fails at the same time, the application will throw an exception because one thread will attempt to access the file while it is being written to by another.
In the code below, I would like to know how to control access to the file at RepeateRequestPath. A RequestSet is a list of strings that represent IDs of the resource I am trying to download.
Dim DownloadCnt As Integer = 0
Dim ParallelOpts As New ParallelOptions()
ParallelOpts.MaxDegreeOfParallelism = 4
Parallel.ForEach(RequestSets, ParallelOpts, Sub(RequestSet)
Try
DownloadCnt += 1
Dim XmlUrl As String = String.Format("{0}{1}{2}", "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=", String.Join(",", RequestSet), "&retmode=xml&rettype=abstract")
DownloadFile(XmlUrl, String.Format("{0}\TempXML{1}.xml", XMLCacheDir, DownloadCnt))
Catch ex As WebException
Using Response As WebResponse = ex.Response
Dim statCode As Integer = CInt(DirectCast(Response, HttpWebResponse).StatusCode)
MessageBox.Show(String.Format("Failed to retrieve XML due to HTTP error {0}. Please hit the 'Retrieve XML' button to re-run retrieval after the current set is complete.", statCode))
If Not File.Exists(RepeatRequestPath) Then
File.WriteAllLines(RepeatRequestPath, RequestSet)
Else
File.AppendAllLines(RepeatRequestPath, RequestSet)
End If
End Using
End Try
End Sub)
The usual way to protect a shared resource in VB.NET is to use SyncLock.
So, you would create a lock object before the Parallel.ForEach() loop:
Dim lock = New Object
and then you would use that inside the loop:
SyncLock lock
File.AppendAllLines(RepeatRequestPath, RequestSet)
End SyncLock
Also note that you can use AppendAllLines() even if the file doesn't exist yet, so you don't have to check for that.
You need to use a semaphore to control access to a shared resource. You want only one thread to access the error file at one time, so initialize the semaphore to only allow 1 thread in. Calling _pool.WaitOne should seize the semaphore, and then release it once it finishes creating/writing to the file.
Private Shared _pool As Semaphore
_pool = = New Semaphore(0, 1)
Dim DownloadCnt As Integer = 0
Dim ParallelOpts As New ParallelOptions()
ParallelOpts.MaxDegreeOfParallelism = 4
Parallel.ForEach(RequestSets, ParallelOpts, Sub(RequestSet)
Try
DownloadCnt += 1
Dim XmlUrl As String = String.Format("{0}{1}{2}", "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=", String.Join(",", RequestSet), "&retmode=xml&rettype=abstract")
DownloadFile(XmlUrl, String.Format("{0}\TempXML{1}.xml", XMLCacheDir, DownloadCnt))
Catch ex As WebException
Using Response As WebResponse = ex.Response
Dim statCode As Integer = CInt(DirectCast(Response, HttpWebResponse).StatusCode)
MessageBox.Show(String.Format("Failed to retrieve XML due to HTTP error {0}. Please hit the 'Retrieve XML' button to re-run retrieval after the current set is complete.", statCode))
_pool.WaitOne()
Try
If Not File.Exists(RepeatRequestPath) Then
File.WriteAllLines(RepeatRequestPath, RequestSet)
Else
File.AppendAllLines(RepeatRequestPath, RequestSet)
End If
Catch ex as Exception
'Do some error handling here.
Finally
_pool.Release()
End Try
End Using
End Try
End Sub)
svick's solution is almost right. However if you need to protect access to a shared variable you also need to declare your lock object as shared at class level.
This works correctly:
Friend Class SomeClass
Private Shared _lock As New Object
Private Shared sharedInt As Integer = 0
Sub Main()
SyncLock _lock
sharedInt += 1
End SyncLock
End Sub
End Class
If you use a non-shared lock object, synclock will protect the variable only from multiple accessing threads within the same instance, not across instances.
I get this error at the Commit of a transaction in a desktop application:
This OleDbTransaction has completed; it is no longer usable.
Other posts I have seen with similar error suggests this can occur if it takes a long time, or contains large amounts of data. This is not the case here. Logging tells me it takes 140 ms from Begin to Commit and about 10 commands executed inside the transaction.
It is using an Oracle database.
This class is a simplified version of my database class:
Class MyDatabase
Private mConnection AS OleDbConnection
Private mTransaction AS OleDbTransaction
Function Value(ByVal piSql As String) As Integer
Try
Dim lCommand As OleDbCommand
lCommand = New OleDbCommand(piSql, mConnection)
If mTransaction IsNot Nothing Then
lCommand.Transaction = mTransaction
End If
Dim lValue = lCommand.ExecuteScalar()
If Not lValue Is Nothing Then
WriteLog(lValue.ToString(), 3, "Value Returned")
Return lValue.ToString()
Else
WriteLog("<null>", 3, "Value Returned (null)")
Return ""
End If
Catch ex As Exception
WriteLog(ex.Message)
End Try
End Function
Function ExecuteSql(ByVal piSql As String) As Integer
Try
Dim lCommand As OleDbCommand
lCommand = New OleDbCommand(piSql, mConnection)
If mTransaction IsNot Nothing Then
lCommand.Transaction = mTransaction
End If
Return lCommand.ExecuteNonQuery()
Catch ex As Exception
WriteLog(ex.Message)
End Try
End Function
Public Sub BeginTransaction()
Try
mTransaction = mConnection.BeginTransaction()
Catch ex As Exception
WriteLog(ex.Message)
End Try
End Sub
Public Sub Commit()
If Not mTransaction Is Nothing Then
Try
mTransaction.Commit()
Catch ex As Exception
WriteLog(ex.Message)
End Try
mTransaction.Dispose()
mTransaction = Nothing
End If
End Sub
Public Sub Rollback()
If Not mTransaction Is Nothing Then
Try
mTransaction.Rollback()
Catch ex As Exception
WriteLog(ex.Message)
End Try
mTransaction.Dispose()
mTransaction = Nothing
End If
End Sub
End Class
Calling code (simplified):
mDatabase.BeginTransaction()
mOrderId = mDatabase.Value("select max(order_id) from ler_order")
mDatabase.ExecuteSql("insert into ler_order (order_id,status,message,plotter,reference,paper_size,orientation,plot_scale,format,usr,email,no_of_copies,date_time,uservar1,uservar2,uservar3,ell,nll,eur,nur,coord_type,graveforespoergselanmodningid,graveforespoergselnr,oprettetdato,aendretdato,graveartnavn,andengraveart,lek_virksomhed,lek_navn,lek_adresse,lek_postnr,lek_postdistrikt,lek_land,lek_telefon,lek_mobiltelefon,lek_telefax,lek_email,ga_navn,ga_adresse,ga_postnr,ga_postdistrikt,ga_land,ga_telefon,ga_mobiltelefon,ga_telefax,ga_email,gak_id,gak_virksomhed,gak_navn,gak_adresse,gak_postnr,gak_postdistrikt,gak_land,gak_telefon,gak_mobiltelefon,gak_telefax,gak_email,emailafsendt,konverteringsstatus) values (101633,0,null,'LER','10d6d8bc-b9b2-44bb-84bf-ceca42a0970a',null,0,0,null,null,null,0,'09-05-2011 13:25:33',null,null,'VAND',-259954.967,145092.123,-259802.657,145147.225,1,'10d6d8bc-b9b2-44bb-84bf-ceca42a0970a',425950,'09-05-2011 13:20:27','09-05-2011 13:20:58','Gravemaskine',null,null,'Ledningsoplysning','Kokbjerg',null,'Kolding',null,'59 23 44 55',null,null,'info#mail.com','FORSYNINGSLEDNINGER','vej 12','1700','Nyberg','NO','21491697',null,null,'mail#info.com','051055f4-ea2a-4cb3-a016-6f6477e6a342','MUNCK FORSYNINGSLEDNINGER A/S','Jon Andersen','vej 38 B','7100','Vejle','NO',null,'23681515','76409220','mail#info.com','09-05-2011 13:20:58','OK')")
mDatabase.ExecuteSql("delete from ler_order_coord where order_id = 101633")
mDatabase.ExecuteSql("insert into ler_order_coord (order_id,polygon_no,seq_no,east,north) values (101633,1,1,-259954.967,145120.599)")
mDatabase.ExecuteSql("insert into ler_order_coord (order_id,polygon_no,seq_no,east,north) values (101633,1,2,-259951.933,145092.123)")
mDatabase.ExecuteSql("insert into ler_order_coord (order_id,polygon_no,seq_no,east,north) values (101633,1,3,-259802.657,145111.956)")
mDatabase.Commit() 'This is where the error occurs
EDIT:
See my answer for how I solved this.
I have a follow-up question on this: Is it not allowed to run a select inside a transaction like this? Or can it be done by running the transaction in a specific isolation level (I see that the BeginTransaction method has an optional parameter for doing this) ? ..Or some other sollution..? In my case, it was not a problem to move the select to run before the transaction started, but what if you need to run selects that must run inside the transaction?
I found an answer to another question, which helped me along the way:
How to check if Dotnet transaction is rolled back?
I implemented the TransactionScope to have better control on my transaction, and I noticed a new error message in the log saying :
The Transaction Manager is not available. (Exception from HRESULT: 0x8004D01B)
This error was triggered on the select statement:
mOrderId = mDatabase.Value("select max(order_id) from ler_order")
I moved the select to before the transaction begins, and now it works!
EDIT:
I discovered that what triggered this error might not have been the use of TransactionScope, but that I tried to set lCommand.Transaction = mTransaction on the select command. This is apparently not allowed when the command is not an action command. This is however not the original problem, because the error on Commit was there before I tried to set the active transaction on the select command. This is just something I tried along the way trying to fix it.