3

(Scope: C# and VB.NET although code here is in VB.NET.)

My WinForms application is working well until first suspend and resume of the PC. After resume, certain event handlers are ignored.

Logging mechanism logs first suspend and first resume. Similarly, breakpoint in VisualStudio stops in Sub PowerModeChanged() after first suspend and resume. But they never do it again.

Sub PowerModeChanged() never gets called again.
Sub FeedRawInput() never gets called again and keystrokes go to standard WinForms handlers which are otherwise inactive in my app.

I'm not messing with any system tweaks, it is pretty standard WinForms MDI application. Any idea what can be killing some internal bindings so events get never called after resume?

If I restart the application, everything is immediately running fine again... until suspend-resume.

Sub Initialize() 'called from MainForm_Load()
    If AreAllSettingsMissing() Then PushAllDefaultSettings() 'needs database connections available
    AddHandler Microsoft.Win32.SystemEvents.PowerModeChanged, AddressOf PowerModeChanged

    _rawInput = New RawInputProcessor.RawFormsInput(My.Application.OpenForms(0),
                                                    RawInputProcessor.RawInputCaptureMode.Foreground)
    AddHandler _rawInput.KeyPressed, AddressOf FeedRawInput
End Sub

'my attempt to log suspend/resume and to rebuild USB scanner support
Sub PowerModeChanged(sender As Object, e As Microsoft.Win32.PowerModeChangedEventArgs)
    clsLogging.Log(String.Format("{0}:{1}:{2}", sender.ToString(), [Enum].GetName(GetType(Microsoft.Win32.PowerModes), e.Mode), _rawInput.ToString()))
    If e.Mode <> Microsoft.Win32.PowerModes.Resume Then Return
    'bring all down
    If _rawInput Is Nothing Then Return
    _rawInput.Dispose()
    _rawInput = Nothing
    RemoveHandler _rawInput.KeyPressed, AddressOf FeedRawInput 'problem 1 - called too late

    'restore all back
    _rawInput = New RawInputProcessor.RawFormsInput(My.Application.OpenForms(0),
                                                    RawInputProcessor.RawInputCaptureMode.Foreground)
    AddHandler _rawInput.KeyPressed, AddressOf FeedRawInput
End Sub


Sub FeedRawInput(sender As Object, e As RawInputProcessor.RawInputEventArgs)
    'push raw input key attributes into processing queue where 
    'each detected USB keyboard has its own async processing queue,
    'marshalling complete keystroke or scan back to main thread - standard stuff
End Sub

Regarding RawInput library, I have ported this one (C#->VB) which is a fork of this one. At the moment I don't know if creation of hidden window (what library does for catching messages) can play any role in the problem. (But it handles keyboard, so why also power event is ignored?) When I've tested library in small WPF project, it could survive resume well. WinForms testing of standalone library I didn't do yet.

If you have any quick hints where to check instead of lengthy dismantling of everything, separating pieces of app for easier diagnostics etc. please let me know.

Update 9 hours later:

  • slight improvement was achieved when line marked problem 1 was moved before Dispose(). Debugger is no longer strangely losing tracking.
  • handler PowerModeChanged() is always called (no longer ignored) after I removed my multi-threaded USB keyboard decoder. Now it can survive suspend-resume.
Community
  • 1
  • 1
miroxlav
  • 11,796
  • 5
  • 58
  • 99
  • 2
    Power management is one of the buggiest parts of Windows. Caused by system builders that try to make their product distinct from their competitors' even though they use the exact same hardware. Not much you can do about it, you're lucky to catch the problem early. SystemEvents is a bug factory too btw, caused by displaying UI on more than one thread. Giving it no option but fire the event on the wrong thread, that rarely turns out well. – Hans Passant Feb 05 '15 at 16:14
  • +20 Hans (if I could...) for pointing to threads. After I temporarily removed multi-threaded USB keyboard decoder, `PowerMode` trapping is never ignored any more, works OK. Can the problem you described possibly be avoided by terminating all backround worker threads before suspending? (In this case, it will be possible without issues.) I'll try to find out. – miroxlav Feb 06 '15 at 00:27
  • @HansPassant – @ bugs you described above – does this imply that any multi-threaded .NET application which has main UI thread must be always put to suspend using workaround like this? (stopping other threads...) – miroxlav Feb 06 '15 at 15:59
  • 1
    No, this only goes wrong in programs that display UI on more than one thread. To which the proper advice should be "don't do that!" or "you'd better know what you're doing". Almost nobody does. – Hans Passant Feb 06 '15 at 16:01
  • @HansPassant - I was carefully keeping all UI in single thread, using `Send()`/`Post()` to marshall calls from other threads back. So maybe I had some other bug. Anyway, the workaround helped. – miroxlav Feb 06 '15 at 16:05

1 Answers1

0

I didn't find solution (workaround?) other than to terminate all worker threads and remove global handlers on receiving Suspend event. After computer resume, on receving Resume event in my application I can re-create all listeners and re-add global handlers. (Listeners were not enough, handlers needed "refresh", too.) After applying this approach it started to work correctly.

Sub PowerModeChanged(sender As Object, e As Microsoft.Win32.PowerModeChangedEventArgs)
    clsLogging.Log(String.Format("{0}:{1}:{2}",
                                 sender.ToString(),
                                 [Enum].GetName(GetType(Microsoft.Win32.PowerModes), e.Mode),
                                 Threading.Thread.CurrentThread.GetHashCode()))
    Select Case e.Mode
        Case Microsoft.Win32.PowerModes.Suspend
            My.Application.Scanners.Suspend()
                'inside: 1) send termination request to all threads
                '        2) clear USB keyboard map and any other references
                '        3) remove global raw keyboard event handler
                '        4) destroy instance of rawInput engine
        Case Microsoft.Win32.PowerModes.Resume
            My.Application.Scanners.Resume()
                'inside: undo (rebuild) steps 4, 3, 2, 1 seen in Suspend()
    End Select

    'debug printout: see list of all thread-powered device readers
    Dim logEntry As String = ""
    For Each key As IntPtr In My.Application.Scanners.Devices.Keys
        logEntry &= String.Format("{0} {1} ", key, My.Application.Scanners.Devices(key))
    Next
    clsLogging.Log(logEntry)
End Sub
miroxlav
  • 11,796
  • 5
  • 58
  • 99