I'm having trouble performing a simple test of the Transient Fault Handling Application Block, so I must be missing something silly.
Based on the information in http://msdn.microsoft.com/en-us/library/hh680927(v=pandp.50).aspx and
http://azuretable.blogspot.com/2012/08/unit-testing-transient-errors-in-azure.html I have created the mock class below:
Public Class DBUtils_TestRetryPolicy
' see http://msdn.microsoft.com/en-us/library/hh680927(v=pandp.50).aspx
' see http://azuretable.blogspot.com/2012/08/unit-testing-transient-errors-in-azure.html
Public Class TransientMock
Implements ITransientErrorDetectionStrategy
Public Function IsTransient(ex As System.Exception) As Boolean Implements Microsoft.Practices.TransientFaultHandling.ITransientErrorDetectionStrategy.IsTransient
Return (True)
End Function
End Class
Private Shared mockExceptionCounter As Integer = 0
Private Shared retryLog As String = ""
Private Sub ThrowException()
mockExceptionCounter += 1
Throw New Exception("This is as mock exception to test retries")
End Sub
Public Function TestRetryLogic() As String
Dim theRetryStrategy = New Incremental(6, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2)) ' should result in retries at 0, 1, 3, 5, 7, 9 seconds (25 seconds total)
theRetryStrategy.FastFirstRetry = True
Dim theRetryPolicy = New RetryPolicy(Of TransientMock)(theRetryStrategy)
AddHandler theRetryPolicy.Retrying, AddressOf OnMockConnectionRetry
mockExceptionCounter = 0
retryLog = ""
Try
theRetryPolicy.ExecuteAction(New System.Action(AddressOf ThrowException))
Catch ex As Exception
' here we should have the last exception thrown after all the retries
Dim a As Integer = 231234234
End Try
Return (retryLog)
End Function
Private Sub OnMockConnectionRetry(sender As Object, e As RetryingEventArgs)
retryLog += DateTime.UtcNow.ToString + " [" + mockExceptionCounter.ToString() + "] -> CurrentRetryCount [" + Cstr(e.CurrentRetryCount) + "]" + "Delay (ms) [" + CStr(e.Delay.TotalMilliseconds) + "]" + "LastException [" + e.LastException.Message + "]"
End Sub
End Class
All I do in my code is to instantiate this class and call TestRetryLogic().
I ran the Visual Studio debugger expecting to see a few retries, but what I get is a popup from Visual Studio saying "Exception was unhandled by user code". This happens as soon as I throw inside the method ThrowException(). Of course no retries seem to be happening.
What am I missing?
EDIT: I was failing to cast to string inside OnMockConnectionRetry, so I assume I was throwing an exception inside an (already "running") exception-handling block. By using the tip from Petar I was able to see (and fix) this little problem and now the retries are working as expected.
You need to turn off Common Language Runtime Exceptions by un-checking the User-unhandled option to not get interrupted by VS. This is located under Debug/Exceptions menu.
Related
My VB.NET winforms app runs a timer which creates a background worker to update the objects in an ObjectListView.
In the timer loop, a number of 'device' objects are added to an observable collection (in the backgroundworker_progresschanged event) and (in the backgroundworker_complete event), I use an OLV.SetObjects(allDevices, true) to populate them.
This all works flawlessly. However, the currently selected items in the OLV are lost during the OLV.setobjects so I need to restore them.
To do this, (in the backgroundworker_complete event), I want to access the selecteditems property of the OLV but I keep getting a "Cross-thread operation not valid: Control 'DeviceListView1' accessed from a thread other than the thread it was created on." All attempts at trying to read the selected listviewitems (either by OLV.selecteditems or a loop reading them from the OLV) fail with the cross-thread exception.
I may misunderstand but I thought I could access GUI elements on the backgroundworker_progresschanged and backgroundworker_complete events?
Here's the relevant code:
The PopulateDevices sub is called when the timer is started and will not run again until a specific time has passed. It runs the RunWorkerAsync of the Worker.
Public Sub PopulateDevices()
' Debug
_UpdateCount += 1
' Pause the Update Timer
UpdateTimer.Stop()
' Get the Starting Time of this Update
StartTime = DateTime.Now
' Stop updating the DeviceListView1 ObjectListView
ControlHelper.ControlInvoke(DeviceListView1, Sub() DeviceListView1.BeginUpdate())
' Clear Existing Devices from the List
AllDevices = New TrulyObservableCollection(Of DeviceItem)
' Get the selected devices
'_SelectedDevices = GetSetSelectedDevices(DeviceListView1)
' Prep the BackgroundWorker
PopulateDevicesWorker = New BackgroundWorker
PopulateDevicesWorker.WorkerReportsProgress = True
' Add the Event Handlers
AddHandler PopulateDevicesWorker.DoWork, AddressOf PopulateDevicesWorkerDoWork
AddHandler PopulateDevicesWorker.ProgressChanged, AddressOf PopulateDevicesWorkerProgressChanged
AddHandler PopulateDevicesWorker.RunWorkerCompleted, AddressOf PopulateDevicesWorkerCompleted
' Start the BackgroundWorker
If Not PopulateDevicesWorker.IsBusy Then
PopulateDevicesWorker.RunWorkerAsync()
End If
End Sub
The worker will read a list of devices from a SQLite DB and (in the progresschanged event) populate an observable collection (AllDevices):
Private Sub PopulateDevicesWorkerDoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs)
' We only continue if the \Clients\_Cache File exists and can be read
If Not File.Exists(CacheFilePath) Then
Exit Sub
End If
' Create a new SQLite Connection & Connect to database
Dim DBC As SQLiteDatabase = OpenDB(CacheFilePath)
If Not IsNothing(DBC) Then
' Count the Rows in the \Clients\_Cache file
Dim RowCount As Integer = CountTableRows(DBC, "_Cache")
' Set the SQL Query
SqlQuery = "SELECT * FROM _Cache WHERE Archived = #Archived"
' Create the SQLite Command
Using SQLitecmd As SQLiteCommand = New SQLiteCommand(SqlQuery, DBC.Connection)
SQLitecmd.Parameters.AddWithValue(String.Empty & "Archived", IIf(fMain.ButtonItem_VIEWARCHIVE.Checked, "True", "False"))
Using SQLiteReader = SQLitecmd.ExecuteReader()
Dim Counter As Integer = 0
' Read All Properties into the Array
While SQLiteReader.Read()
Using DeviceItem As New DeviceItem
With DeviceItem
' Get the Device Info here
End With
' Report progress at regular intervals
PopulateDevicesWorker.ReportProgress(CInt(100 * Counter / RowCount), DeviceItem)
' Increment the Counter (for Progress)
Counter += 1
End Using
End While
End Using
End Using
End If
CloseDB(DBC)
End Sub
Here is the WorkerProgressChanged event. It adds the current device (from the worker) into the observable collection (AlLDevices)
Private Sub PopulateDevicesWorkerProgressChanged(sender As Object, e As ProgressChangedEventArgs)
' Update Status
LabelItem_STATUS.Text = "Working.. (" & e.ProgressPercentage & "%)"
' Add the Device to Collection
AllDevices.Add(TryCast(e.UserState, DeviceItem))
End Sub
The WorkerCompleted event will set the objects in AllDevices to the OLV (DeviceListView1)
Private Sub PopulateDevicesWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) ' Handles PopulateDevicesWorker.RunWorkerCompleted
' This is producing Cross-Thread error
If Not IsNothing(_SelectedDevices) Then
For Each item As ListViewItem In _SelectedDevices
Debug.Print(item.Text)
Next
End If
' Populate the ObjectListView
ControlHelper.ControlInvoke(DeviceListView1, Sub() DeviceListView1.SetObjects(AllDevices, True))
' Re-enable Form Updates
ControlHelper.ControlInvoke(DeviceListView1, Sub() DeviceListView1.EndUpdate())
' If the refresh rate isn't already set, set it to the time taken to complete the Update PLUS the Seconds specified in the SETTINGS.INI File
Dim difference As TimeSpan = DateTime.Now.Subtract(StartTime)
If UpdateTimeInSeconds = -1 Then
UpdateTimer.Interval = (RefreshRate + difference.TotalSeconds) * 1000
End If
' Restart the Update Timer
UpdateTimer.Start()
End Sub
I was under the impression, that I can update the GUI (get the OLV selecteditems, etc.) from the WorkerProgressChanged and WorkerCompleted backgroundworker events but I get the darn cross-thread error.
I'm also having to INVOKE the BEGIN\END UPDATE as calling them directly produces error.
I have read that the olv.setobjects in ObjectListView 2.91 (the version I am using) should persist the selections but I haven't seen this at all.
Please! What am I missing? Its probably something daft or is there another way of doing this?
If you are not using a Forms.Timer (but Timers.Timer or Threading.Timer) for your UpdateTimer, anything called from the timer "tick" event will run on a different thread.
Thus, PopulateDevices would also be called from a non GUI thread and the BackgroundWorker will run on that thread as well.
Firstly, new thread born independently of my code by external factory.
I have something integer key variable - "CODE". This variable "CODE" I received as result of a lot of calculating and request to DB (and maybe I need multi-threading protection of this "CODE").
I need lock thread only for the same "CODE", if currently "CODE" is handling now, thread with other "CODE" can not locking and handling without obstacle.
Thread with "CODE" now handling and locking need to immediately finish.
Also all this function must be working asynchronously with ASYNC/AWAIT.
I am using .NET Core 6.
It looks like a very common task, but what mechanism do I need to use?
Does code template for this task exist in inet?
This is my solution, but I'm not sure how it working with high uploading. I only try to test this code with high concurrency. If anybody has advice for me, please.
I realize solution with ConcurrentDictionary and locking critical section. To unlock main locker and using secondary locker I created new task.
This is schema of my solution.
Public Sub New(ByVal logger As ILogger(...)
....
WorkingServer = New ConcurrentDictionary(Of Integer, BashJob)()
WorkingVm = New ConcurrentDictionary(Of Integer, BashJob)()
End Sub
Private WorkingServer As ConcurrentDictionary(Of Integer, BashJob)
Private RequestNextJob As New Object
Public Async Function Execute(context As IJobExecutionContext) As Task Implements IJob.Execute
_logger.LogInformation($"Time {Now}")
Interlocked.Increment(Counter)
SyncLock RequestNextJob
' calculate CODE hidden in NextJob
...
Dim NextJob As BashJob = Res1.Result.Item1(0)
Dim Val1 As BashJob
Dim ServerWorking As Boolean = WorkingServer.TryGetValue(NextJob.toServer, Val1)
If Not ServerWorking Then
Dim AddSucess1 = WorkingServer.TryAdd(NextJob.toServer, NextJob)
If AddSucess1 Then
Dim ServerThread = New Thread(Sub() ServerJob(NextJob, New ServerClosure))
ServerThread.Start()
Else
Exit Function
End If
Else
Exit Function
End If
End SyncLock
End Function
Async Sub ServerJob(ByVal Prm As BashJob, ByVal Closure As ServerClosure)
Try
Closure.Res = Sql.ExecNonQuery(...)
...
'main processor
...
WorkingServer.TryRemove(Prm.toServer, Prm)
Catch ex As Exception
_logger.LogInformation($"ServerSsh ({Counter.ToString}) {Now.ToString} Server:{Prm.toServer} {Prm.i}:[{Prm.Command}] GettingError {ex.Message}")
Finally
WorkingServer.TryRemove(Prm.toServer, Prm)
End Try
End Sub
End Class
Public Class ServerClosure
Property Res As Integer
Property Server As ServerBashAsync
Property Connect As Tuple(Of Renci.SshNet.SshClient, Exception, Exception)
Property BashRet As Task(Of String)
Property IsCompleted As Integer
Property IsCompletedWithErr As Integer
End Class
Hello I am running a VBA script in excel 365, and am trying to make an object (day) store a list of custom objects (job) but when trying to input the array of objects I run into an error.
I am using Property handlers to access the private internal array for the day object.
This is how it gets ran, the jobTest function only creates some arbitrary job objects.
Sub dayTest()
jobTest
Debug.Print testJob.GNum
Dim foo As New day
foo.DateBlock = DateValue("14 / 03 / 2020")
Debug.Print TypeName(testJob)
Dim x As Variant
x = foo.JobsForDay(0, testJob)
End Sub
When running this code I get the error:
Wrong number of arguments or property assignment
But when looking at my access methods I created, these don't seem to apply.
Private mDateStored As Date
Private mNumJobs As Long
Private mJobsForDay() As job
Property Get JobsForDay(val As Long) As Variant
JobsForDay = mJobsForDay(val)
End Property
Property Let JobsForDay(val As Long, jobObj As Variant)
mNumJobs = mNumJobs + 1
ReDim Preserve mJobsForDay(mNumJobs)
mJobsForDay(val) = jobObj
End Property
I am trying to call the Let function, however the compiler throws a generic "Expected =" error when I run the code as
foo.JobsForDay(0, testJob)
Which is why I have the variant x accepting all input from the method.
Thanks for any help!
In order to eliminate human error and avoid the tedium of maintaining who-knows-how-many App.config files, I've decided to set all of my Quartz.NET jobs' Log4Net configurations at runtime, completely in code. No App.config files allowed. I've been successful in getting a standalone console app to do this, as demonstrated here.
However, it's a different story in the scheduler itself. For some reason it's not using the configuration that I'm specifying:
Dim oProperties As New NameValueCollection
' ... Set Quartz.NET properties here
BasicConfigurator.Configure(New Logger.DbAppender)
With New StdSchedulerFactory(oProperties)
Manager.Scheduler = .GetScheduler
End With
I'm running it as a TopShelf service and I'd like the Scheduler's logs to go to the same destination as each of the IJobs. Here, Quartz.NET stubbornly continues to send its output to TopShelf's debugging console window (STDOUT).
This construct works in the console app (linked above); why isn't it working here?
OK, got it. (That didn't take long!)
For some reason, the TopShelf/Quartz.NET combo insists on suppressing logging output if the requisite App.config entries are missing.
In this case, since it's the central scheduler we're dealing with and not a plethora of jobs, I'll bite the bullet and go ahead and use an App.config file for this one.
So the root problem remains unsolved; this is just a workaround for the time being. If you know the answer I'd still be pleased to hear from you. Thanks.
OK, got it. (For REAL this time.)
Clarification: the suppression of logging in the absence of a .config file occurs in Common.Logging when run under the TopShelf/Quartz.NET combination. For some reason it consistently refuses to recognize a runtime-loaded Appender, even when the first line of code in the service configures logging. Still a mystery, but not important. (At least not for me, with this latest discovery—YMMV.)
The only logging that's suppressed is that which follows the buildup and teardown of the Common.Logging library itself; everything that occurs in the service itself is still logged.
I was able to get it to work using the code below (in combination with the code linked to above, of course).
As I said, YMMV.
[Service]
Public Module Main
Sub Main()
Try
BasicConfigurator.Configure(New Logger.DbAppender("ConnectionString"))
Logger = LogManager.GetLogger(GetType(Main))
Environment.ExitCode = HostFactory.Run(Sub(Configurator)
Configurator.Service(Of Manager)(Sub(Service)
Service.ConstructUsing(Function(Factory) As ServiceControl
Return New Manager
End Function)
Service.WhenStarted(Function(Manager, HostControl) As Boolean
Return Manager.StartService(HostControl)
End Function)
Service.WhenStopped(Function(Manager, HostControl) As Boolean
Return Manager.StopService(HostControl)
End Function)
End Sub)
Configurator.SetDescription(ServiceInfo.Description)
Configurator.SetServiceName(ServiceInfo.Product)
Configurator.SetDisplayName(ServiceInfo.Title)
Configurator.StartAutomatically()
Configurator.RunAsLocalSystem()
End Sub)
Catch ex As Exception
EventLog.WriteEntry(ServiceInfo.Product, ex.Message, EventLogEntryType.Error, 1, 1, ex.ToBytes)
End Try
End Sub
Public Property Logger As ILog
End Module
[DbAppender] (updated w/add'l constructor that accepts a connection string)
Public Class DbAppender
Inherits AdoNetAppender
Public Sub New()
Me.New(String.Empty)
End Sub
Public Sub New(ConnectionString As String)
If Trim(ConnectionString) <> String.Empty Then
MyBase.ConnectionString = ConnectionString
End If
MyBase.CommandText = Me.CommandText
MyBase.BufferSize = 1
Me.Parameters.ForEach(Sub(Parameter As DbParameter)
MyBase.AddParameter(Parameter)
End Sub)
Me.ActivateOptions()
End Sub
Protected Overrides Function CreateConnection(ConnectionType As Type, ConnectionString As String) As IDbConnection
Return MyBase.CreateConnection(GetType(System.Data.SqlClient.SqlConnection), MyBase.ConnectionString)
End Function
Private Overloads ReadOnly Property CommandText As String
Get
Dim _
sColumns,
sValues As String
sColumns = Join(Me.Parameters.Select(Function(P As DbParameter) P.DbColumn).ToArray, ",")
sValues = Join(Me.Parameters.Select(Function(P As DbParameter) P.ParameterName).ToArray, ",")
Return COMMAND_TEXT.ToFormat(sColumns, sValues)
End Get
End Property
Private ReadOnly Property Parameters As List(Of DbParameter)
Get
Parameters = New List(Of DbParameter)
Parameters.Add(Me.Date)
Parameters.Add(Me.Thread)
Parameters.Add(Me.Level)
Parameters.Add(Me.Source)
Parameters.Add(Me.Message)
Parameters.Add(Me.Exception)
End Get
End Property
Private ReadOnly Property [Date] As DbParameter
Get
Return New DbParameter("Date", New DbPatternLayout(PATTERN_DATE), DbType.Date, 0)
End Get
End Property
Private ReadOnly Property Thread As DbParameter
Get
Return New DbParameter("Thread", New DbPatternLayout(PATTERN_THREAD), DbType.String, 255)
End Get
End Property
Private ReadOnly Property Level As DbParameter
Get
Return New DbParameter("Level", New DbPatternLayout(PATTERN_LEVEL), DbType.String, 50)
End Get
End Property
Private ReadOnly Property Source As DbParameter
Get
Return New DbParameter("Source", New DbPatternLayout(PATTERN_SOURCE), DbType.String, 255)
End Get
End Property
Private ReadOnly Property Message As DbParameter
Get
Return New DbParameter("Message", New DbPatternLayout(PATTERN_MESSAGE), DbType.String, 4000)
End Get
End Property
Private ReadOnly Property Exception As DbParameter
Get
Return New DbParameter("Exception", New DbExceptionLayout)
End Get
End Property
Private Const PATTERN_MESSAGE As String = "%message"
Private Const PATTERN_THREAD As String = "%thread"
Private Const PATTERN_SOURCE As String = "%logger.%M()"
Private Const PATTERN_LEVEL As String = "%level"
Private Const PATTERN_DATE As String = "%date{yyyy-MM-dd HH:mm:ss.fff}"
Private Const COMMAND_TEXT As String = "INSERT INTO Log ({0}) VALUES ({1})"
'======================================================================================
' Available patterns:
' http://logging.apache.org/log4net/release/sdk/log4net.Layout.PatternLayout.html
'======================================================================================
End Class
In the code below, StartSocketListener has been started as a Thread. Sometimes, the processing of a message takes long enough that a couple of messages stack up (or at least that appears to be the case). I thought I'd take the socket and start another thread with it. I thought that passing the _HostConnection into the task would make concurrent instances of MessageFunction thread safe. Not so, it appears. I regularly get two kinds of errors: one is at _HostReader.ReadString, where I either read past the end of the stream or read only a portion of the stream (as if something else has consumed part of it). The other is at _HostWriter.Write, where I get "an established connection was aborted." Is it obvious where I've failed to get the thread safety that I was expecting? [I am a C# developer, so the VB lambda may look awkward :) ]
Private Sub StartSocketListener()
Dim _Listener As TcpListener = Nothing
Dim _HostConnection As Socket
Try
_Listener = New TcpListener(IPAddress.Parse(GetIPAddress), _TCPListenerPort)
_Listener.Start()
While _KeepListening
_HostConnection = _Listener.AcceptSocket
Dim MessageFunction = Sub(socket As Socket)
Dim _ClientIP As String = socket.RemoteEndPoint.ToString
Dim _HostSocketStream As NetworkStream = New NetworkStream(socket)
Dim _HostWriter As BinaryWriter = New BinaryWriter(_HostSocketStream)
Dim _HostReader As BinaryReader = New BinaryReader(_HostSocketStream)
Try
Dim _MsgXML As String = _HostReader.ReadString
If _MsgXML.Trim <> "" Then
If _KeepListening Then
Dim _RetXML As String = ProcessMessage(_MsgXML, _ClientIP)
Try
_HostWriter.Write(_RetXML)
Catch ex As Exception
...
End Try
End If
End If
Catch ex As Exception
...
End Try
_HostReader.Close()
_HostWriter.Close()
_HostSocketStream.Close()
socket.Close()
End Sub
Task.Factory.StartNew(Sub() MessageFunction(_HostConnection))
End While
_Listener.Stop()
Catch ex As Exception
....
End Try
End Sub