I am developing a plugin for an application. I need to implement a low-level keyhook to capture keys. I have tried running the keyhook on the same thread as the main application + plugin, but it's performance is hit and miss. I'm not able to query this with the application's original developer.
Thus, I've implemented the code below to run the keyhook on a backgroundworker. It works reliably, but I'm very conscious of the Application.DoEvents I understand this is bad. The code:
Private WithEvents KeyHookBGW As New BackgroundWorker
Private WithEvents KeyHook As New WindowsHookLib.KeyboardHook
Private Sub KeyHookBGW_DoWork(sender As Object, e As DoWorkEventArgs) Handles KeyHookBGW.DoWork
Dim BgKeyhook As New KeyboardHook
BgKeyhook.InstallHook()
AddHandler BgKeyhook.KeyDown, AddressOf KeyHookBGW_KeyDown
Do While e.Cancel = False
Application.DoEvents()
Loop
End Sub
Private Sub KeyHookBGW_KeyDown(sender As Object, e As WindowsHookLib.KeyboardEventArgs)
KeyHookBGW.ReportProgress(0, e.KeyCode)
End Sub
Private Sub KeyHookBGW_ReportProg(sender As Object, e As ProgressChangedEventArgs) Handles KeyHookBGW.ProgressChanged
pm.Console.WriteLine("Key pressed: " & e.UserState)
End Sub
I'm sure my architecture is bad, but I'm a bit stuck with getting this to work in the development context. I tried using a timer in the background thread, but couldn't get this to work. I'm also developing in .net Framework 3.5 (original application's framework) and thus this limits what I can do with Threads, I think. I've looked into Threads and I am struggling to understand how to run them infinitely and also communicate back to the main thread.
So, basically, I'm needing to run the keyhook on a separate thread for the lifetime of the main application + plugin and feed back any keydown and keyup events into the main thread. Any ideas?
Related
My class watches a directory for incoming files. It does do so with a FileSystemWatcher object, only monitoring the FSW's Created events.
On a Created event, I start a potentially time-consuming process (file-deserialization is needed, sending an event to the client using my class, in which all sorts of things might happen). Thus, I start a BackgroundWorker object to do all this work, ultimately culminating in the received file's removal.
However, during all this work, new files may appear. In the Created event I check, if the BGW is still busy, and if so, I just store the fully qualified name in a queue for later consumption.
Public Sub New(Path As String)
FSM = New FileSystemWatcher
With FSW
.Path = Path
AddHandler .Created, AddressOf pFileArrived
End With
BGW = New BackgroundWorker
With BGW
.WorkerReportsProgress = False
.WorkerSupportsCancellation = False
AddHandler .DoWork, AddressOf BGW_DoWork
AddHandler .RunWorkerCompleted,
AddressOf BGW_RunWorkerCompleted
End With
End Sub
Private Sub pFileArrived(sender As Object, e As FileSystemEventArgs)
pNotifyClient(e.FullPath)
End Sub
Private Sub pNotifyClient(sFullPath As String)
If Not BGW.IsBusy Then
BGW.RunWorkerAsync(sFullPath)
Else
MyQueue.Enqueue(sFullPath)
End If
End Sub
Private Sub BGW_DoWork(ByVal sender As Object,
ByVal e As DoWorkEventArgs)
'...
End Sub
But how can I find out, when the BGW is done?
I know, that there is the RunWorkerCompleted event. However, this event is fired from a real BGW instance still existing, so I can not go on and simply call it again from within the event handler.
Private Sub BGW_RunWorkerCompleted(sender As Object,
e As RunWorkerCompletedEventArgs)
'This won't work.
If MyQueue.Count > 0 Then
BGW.RunWorkerAsync(MyQueue.Dequeue)
End If
End Sub
What is the proper way of doing such things? Initializing a timer does spring to mind, but it doesn't seem right. (How much time should I give it? Should I loop for the BGW thread's end?)
Or should I consider another approach than invoking a BGW?
If you want to nuke this problem from the orbit, put an AutoResetEvent(false) somewhere and your BGW's last task should be to evt.Set() and your main thread can do evt.WaitOne(0) to just query the status. If there's a possibility of running multiple BGWs at the same time, you need some data structure to keep track of which ARE is associated with which BGW.
A larger investment would be switch to pure producer-consumer design, which involves a queue (which you already have in a form) and consumer thread(s) to dequeue work items and process them.
I'm creating a vb.net desktop application. This application includes some asynchronous functions. When the user closes the application via the red X in the upper-right corner, there is some logic to possibly run one or more of these async functions. The problem is, the program terminates before they are complete. I figured using "Await" in my call would do that, but apparently not.
I found this thread that talks about using ManualResetEvent, but I'm having trouble understanding all of it, especially since the question is in the context of a console app, and the MSDN documentation the answer links to is about specifying threads, not simply using async tasks. As an attempt at using it anyway, I tried adding this to my main form:
Public resetEvent As ManualResetEvent = New ManualResetEvent(False)
And immediately after the call to one of these functions, I added this (quote includes the call):
Await activeCount.SerializeAsync(activeCount)
resetEvent.WaitOne()
And at the end of my async function itself, before returning the Task, added this:
frmMain.resetEvent.Set()
I don't think I'm using that right, though. The program still terminates before it's complete anyway.
Even before that, I figured the best place for such a thing would be in ApplicationEvents MyApplication_Shutdown, but I'm not sure how to know if such a function is still running at that point.
So what is the best way to make sure all my async functions complete before the application terminates in this situation?
Thank you!
UPDATE AFTER ACCEPTED ANSWER:
Though F0r3v3r-A-N00b's answer worked, I realized I need to use a dialog in certain cases. I couldn't call that within the background worker because the dialog is on the GUI thread, not the background thread. I tried moving things around so I'd call the dialog first, then make the background worker and all that, but for whatever reason I couldn't get it to work.
Long story short, I got around it by simply making a synchronous version of my functions, and so I could say 'if the user terminated the program and I need to call any of these functions before closing, call the synchronous versions instead'. That works. Thanks!
Try this. Create a new project. Add 1 label and backgroundworker to your form. Paste this in your form's code area:
Public Class Form1
Dim taskCompleted As Boolean = False
Dim taskIsrunning As Boolean = False
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Threading.Thread.Sleep(5000)
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
taskCompleted = True
taskIsRunning = False
Label1.Text = "Background task completed."
Me.Close()
End Sub
Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
If taskIsRunning Then
e.Cancel = True
Exit Sub
End If
If Not taskCompleted Then
taskIsRunning = True
Label1.Text = "Starting background task."
BackgroundWorker1.RunWorkerAsync()
Label1.Text = "Background task is running."
e.Cancel = True
End If
End Sub
End Class
I'm new to Visual Basic and overall kind of new to coding in general.
Currently I work on a program which uses a filewatcher. But If I try this:
Public Class Form1
Private WithEvents fsw As IO.FileSystemWatcher
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
fsw = New IO.FileSystemWatcher("PATH")
fsw.EnableRaisingEvents = True
' fsw.Filter = "*.settings"
End Sub
Private Sub GetSettingsFromFile()
Some Code
More Code
CheckBox1.Checked = True
End Sub
Private Sub fsw_Changed(sender As Object, e As FileSystemEventArgs) Handles fsw.Changed
fsw.EnableRaisingEvents = False 'this is set because the file is changed many times in rapid succesion so I need to stop the Filewatcher from going of 200x (anyone has a better idea to do this?)
Threading.Thread.Sleep(100)
GetSettingsFromFile()
fsw.EnableRaisingEvents = True 'enabling it again
End Sub
End Class
But when I do this (trying to change anyhting in the form) I get this error:
System.InvalidOperationException (WinForms.IllegalCrossThreadCall)
It wont stop the program from working, but I want to understand what is wrong here and why the debugger is throwing this at me
regards
The event is being raised on a secondary thread. Any changes to the UI must be made on the UI thread. You need to marshal a method call to the UI thread and update the UI there. Lots of information around on how to do that. Here's an example:
Private Sub UpdateCheckBox1(checked As Boolean)
If CheckBox1.InvokeRequired Then
'We are on a secondary thread so marshal a method call to the UI thread.
CheckBox1.Invoke(New Action(Of Boolean)(AddressOf UpdateCheckBox1), checked)
Else
'We are on the UI thread so update the control.
CheckBox1.Checked = checked
End If
End Sub
Now you simply call that method wherever you are and whatever thread you're on. If you're already on the UI thread then the control will just be updated. If you're on a secondary thread then the method will invoke itself a second time, this time on the UI thread, and the control will be updated in that second invocation.
I try to run a timer from my winform application. For some reason the function that should run on the timer's tick (IsTimeOffsetValid) is not called nor stopped on break point, and basically nothing happens. I attached a code sample below.
I appreciate the help.
Module Module1
Sub main()
Dim OutputForm As New Form17
Application.Run(OutputForm)
End Sub
End Module
Public Class Form17
Private TimerServerOffset As New System.Timers.Timer
Private Sub Form17_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
AddHandler TimerServerOffset.Elapsed, New System.Timers.ElapsedEventHandler(AddressOf IsTimeOffsetValid)
TimerServerOffset.Interval = 1
TimerServerOffset.Start()
End Sub
Private Sub IsTimeOffsetValid()
MsgBox("IsTimeOffsetValid")
End Sub
End Class
Apart from errors in the code that you posted there are other issues with the design.
Read this question: System.Timers.Timer vs System.Threading.Timer
The callback is called on a worker thread (not the UI thread) so displaying a message box could be a big problem.
then switch to a more fitting timer. If all you want to do is validate the inputs every second, switch to the System.Windows.Forms.Timer. The tick handler runs on the UI thread so you can change the UI in the handler.
Then consider changing the interval a message box popping up every millisecond is not possible and not user friendly.
Finally, I would suggest NOT using a timer for this: just handle changes to the input fields and respond to changed inputs or use the standard validation events of the WinForms controls. This is much cheaper (on the CPU) and will not mess with the focus.
Wanna ask if how can I can create a delay without using System.Thread.Sleep(interval).
My apps serves as server runs in a endless loop that reads SMSINBOX using serialport to interface with a GSM MODEM.. readSMSINBOX() is followed by a delay ... The problem here is the delay thats kills me.... It hangs all the controls of my app. Other functions aside from reading, is sending, userMANAGEMENT, console, etc. Any idea how I can implement a delay that consumes time WITHOUT hanging the app by using SYSTEM.TIMERS or SYSTEM.THREADING or BACKGROUNDworker... Please... I can't post all my codes here.. Don't know either how to publish my code in a live site. Just a pure NOOB..
Well the timer should do exactly what you need (it doesn't block the UI).
Exact implementation of timer depends on version of VB.NET you're using but
you can find a good (and downloadable) example of using timers here: Working with Timer Control in VB.NET
You'll have to import System.Timers
myTimer can be variable in the code of main form
Timer myTimer
You can set it up and start it in Form1_load
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs)
myTimer = new Timer(30000)
myTimer.Enabled = true
addhandler myTimer.Tick,addressof OnTimerEvent
End Sub
And your (tick) handler would be something like:
Private Sub OnTimerEvent(ByVal sender As System.Object, ByVal e As System.EventArgs)
'here you can call you communication routines
End Sub