2014-03-28 18 views
4

我想提出一個交互式的基準程序,在控制檯模式下工作,並希望能夠通過按Ctrl + C或CTRL + BREAK一度中斷當前計算,而隨後的壓制應該照常進行,終止程序。但是,當沒有計算當前正在執行時,首先按Ctrl + C鍵應該會導致終止。刪除Console.CancelKeyPress處理程序,一旦禁用任何處理日後

起初,這看起來像一個簡單的任務。我創建了明顯EnableHandler()DisableHandler()程序靜態類,並與行爲依賴於以前的狀態的處理函數:如果中斷已請求,然後就下臺,並返回,否則設置arg.Cancel,提高中斷請求標誌。因此,當基準開始時,它啓用處理程序,然後檢查每個主要週期的中斷標誌;完成後,它會禁用處理程序,並根據需要將中斷請求傳播到最上面的調用方。

但是,這種方法只能一次:首次(不管是它曾經解僱與否)去除處理後重新安裝它之後沒有效果了 - 操作不產生錯誤,但當事件發生時處理程序從不接收控制。這是.NET事件處理的預期行爲嗎?

在SO和其他論壇上有關於Console.CancelKeyPress的很多主題,但幾乎沒有一個人認爲刪除處理程序,因此毫無疑問,他們沒有遇到麻煩。儘管如此,在「How to use ConsoleCancelEventHandler several times」中提到了一些類似的問題,但是該應用程序是一個複雜的GUI前端,用於根據需要啓動的幾個外部控制檯實用程序,並且該問題顯然與第二次嘗試添加處理程序時引發的顯式異常, - 這不是我的情況。

在被告知安慰各種.NET版本操作相關先前存在的bug,如果這樣的行爲是不正常的,通過.NET故障引起的或特定於Visual Basic,我不會感到驚訝,(我不是確定VB.NET中的AddHandlerRemoveHandler是否完全等效於C#中的+=-=事件操作符)。我目前在安裝了所有可用更新的Windows 7 x86-64上使用.NET 4.5和Visual Basic 2012。

作爲一種解決方法,我實際上並不刪除DisableHandler()例程中的處理程序,而是簡單地切換標誌:當此標誌被清除時,處理程序立即返回,就好像未安裝。但是,這種方法對我來說看起來很難看,我希望通過適當的方式解決問題並實現目標。

PS。由克里斯·達納韋要求當前代碼:

' Usage example 
Dim fAbort As Boolean = False 
BreakHandler.Enable() 
For Each oImplementation As Implementation In oCompetition.Implementations 
    Benchmark(oImplementation) 
    If BreakHandler.AbortRequested() Then fAbort = True : Exit For 
Next 
BreakHandler.Disable() 
Return Not fAbort 


Public Class BreakHandler 

    Protected Shared AbortFlag As Boolean = False 
    Protected Shared HandlerInstalled As Boolean = False 
    Protected Shared HandlerEnabled As Boolean = False 

    Public Shared ReadOnly Property AbortRequested() As Boolean 
     Get 
      If AbortFlag Then 
       AbortFlag = False 
       Return True 
      Else 
       Return False 
      End If 
     End Get 
    End Property 

    Public Shared Sub Enable() 
     If HandlerEnabled Then Return 
     If Not HandlerInstalled Then 
      AddHandler Console.CancelKeyPress, AddressOf Handler 
      HandlerInstalled = True 
     End If 
     HandlerEnabled = True 
    End Sub 

    Public Shared Sub Disable() 
     AbortFlag = False 
     If Not HandlerEnabled Then Return 
     ' This is where the handler was removed originally. 
     'RemoveHandler Console.CancelKeyPress, AddressOf Handler 
     HandlerEnabled = False 
    End Sub 

    Protected Shared Sub Handler(ByVal sender As Object, ByVal args As ConsoleCancelEventArgs) 
     If (Not HandlerEnabled) OrElse AbortFlag Then 
      Return ' Stand down, allow complete abortion. 
     Else 
      Console.Out.WriteLine("Will abort on next cycle. Press Break again to quit.") 
      AbortFlag = True 
      args.Cancel = True 
     End If 
    End Sub 

End Class 
+0

你可以發佈一個簡短但完整的程序來演示這個問題嗎?沒有一些代碼,它將很難幫助你。 –

回答

5

是的,這是在控制檯類的錯誤。存在於所有.NET Framework版本中,包括4.5.1。這是一個相當愚蠢的錯誤,第一次註冊一個事件處理函數時,它會安裝一個回調函數,以便Windows得到要引發的事件。當您刪除事件處理程序,並且沒有其他事件處理程序時,它會卸載回調。但忘記重置內部「我安裝到回調」狀態變量。所以當你再次調用AddHandler時,它不會重新安裝回調。

您可以在connect.microsoft.com報告錯誤,讓我知道如果你不想花時間,我會照顧它。

有它一個愚蠢的解決方法,你只需要防止再次卸載回調。你可以通過註冊一個虛擬事件處理程序來完成。將這行代碼放在Main()方法的頂部:

AddHandler Console.CancelKeyPress, Sub(s, e) 
             End Sub 

但是當然你的解決方法也很好。請注意,您的代碼有其他問題,布爾變量不是同步對象。 CancelKeyPress事件在另一個線程上運行,因此不能保證您的主線程可以看到值更改。在Release版本中運行程序並使用x86抖動時,這可能會出現故障。最低要求是將布爾變量聲明爲易失性因此,抖動不會將變量存儲在CPU寄存器中,但始終會將其從內存中重新加載,但VB.NET語言沒有此語法。你應該使用一個真正的同步對象,一個ManualResetEvent是正確的。

+0

微軟Connect中的Bug#524889在2010年被標記爲已修復。如果這是另一個,那麼,好吧,我沒有足夠強大的影響MS人員。至於同步問題,我一般都知道這個話題,但我認爲32位操作符總是原子的,甚至MSVC(更不用說VB.NET)會立即將寄存器值保存回內存,所以最差的這裏的情況將是錯過接近中斷請求並等待另一輪。還是我誤會了? (無論如何,我會按照你的建議去做,因爲它可以提供更高的透明度而不會產生大量開銷。) –

+1

原子性在這裏不是問題。例程優化是將變量值存儲在CPU寄存器中。一個重要的因爲記憶慢。它給出了* volatile *關鍵字在C和C#中的含義。當循環很緊且需要註冊表的其他職責的代碼很少時,它會出錯。不,#524889不相關。 –

+2

提交爲https://connect.microsoft.com/VisualStudio/feedback/details/842578/console-cancelkeypress-event-doesnt-fire –