2012-03-12 62 views
12

在我目前的項目有一個表單類,它看起來是這樣的:解決問題「無法訪問處置的對象」。例外

public partial class FormMain : Form 
{ 

    System.Timers.Timer timer; 
    Point previousLocation; 
    double distance; 

    public FormMain() 
    { 
     InitializeComponent(); 

     distance = 0; 
     timer = new System.Timers.Timer(50); 
     timer.AutoReset = true; 
     timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed); 
     timer.Start(); 
    } 

    private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) 
    { 
     if (previousLocation != null) 
     { 
      // some code 

      UpdateDistanceLabel(distance); 
      UpdateSpeedLabel(v); 
     } 

     previousLocation = Cursor.Position; 
    } 

    private void UpdateDistanceLabel(double newDistance) 
    { 
     if (!lblDistance.IsDisposed && !IsDisposed) 
     { 
      Invoke(new Action(() => lblDistance.Text = String.Format("Distance: {0} pixels", newDistance))); 
     } 
    } 

    private void UpdateSpeedLabel(double newSpeed) 
    { 
     if (!lblSpeed.IsDisposed && !IsDisposed) 
     { 
      Invoke(new Action(() => lblSpeed.Text = String.Format("Speed: {0} pixels per second", newSpeed))); 
     } 
    } 

} 

正如你所看到的,我使用的是System.Timers.Timer對象。我知道我可以使用System.Windows.Forms.Timer,但是我仍然對標題中顯示的異常仍然感興趣。它在UpdateDistanceLabel方法中的Invoke調用中被拋出。令我困惑的是它說「不能訪問處置對象:FormMain」,即使我正在檢查它是否被處置。所以這不應該發生。我也嘗試在FormClosing事件中處理Timer對象,並重寫Dispose(bool)並將其放置在那裏,這兩者都不幸根本沒有任何幫助。此外,異常並不總是被拋出,據推測,只有當計時器在程序退出時觸發時纔會被拋出。它仍然發生很多。

我已經看到有大量的線程關於這個,但我已經嘗試過發佈的解決方案,其中大多數涉及檢查IsDisposed屬性 - 這對我不起作用。所以我想我做錯了什麼。

所以我的問題: 爲什麼上面發佈的代碼觸發異常,即使我正在檢查我正在訪問的對象是否處置?

回答

9

有兩種解決方法:要麼吞下異常,並詛咒微軟沒有包括一個TryInvokeTryBeginInvoke方法,或者使用鎖定,以確保在它的使用不嘗試Dispose製造的物體,並沒有嘗試使用該對象,而Dispose正在進行中。我認爲吞下例外情況可能會更好,但有些人對此類事情有內心的反應,使用鎖定可以避免發生例外情況。

9

一個問題是您在調用Invoke之前正在對計時器線程進行檢查。有一種可能的競爭條件,表單可以在檢查之後和被調用的動作執行之前被處置。

你應該在Invoke所調用的方法(在你的情況下爲lambda表達式)內進行檢查。

另一個可能的問題是您正在訪問定時器線程上的Cursor.Position。我不確定這是否有效 - 我會在主線程上執行此操作。您的代碼還包含註釋//some code - 因此您可能已忽略了一些代碼,您還需要檢查。

總的來說,你可能會更好使用System.Windows.Forms.Timer

+0

謝謝。這就是我現在所做的,但是,這並不會影響行爲。 – haiyyu 2012-03-12 14:54:28

6

這是我的解決您的異常,如果你有興趣:

private void FormMain_FormClosing(object sender, FormClosingEventArgs e) 
     { 
      timer.Stop(); 
      Application.DoEvents();  
     } 

.Stop()不.DoEvents()是不夠的,因爲它會釋放對象,而不必等待你的線程來完成其工作。

+0

是的,快速和骯髒,但它會做的伎倆。這顯示了他的根本原因。 – 2012-03-12 15:23:54

+0

調用'Application.DoEvents'不能保證你避免了潛在的競爭條件,儘管它可能會使它不太可能。 – Joe 2012-03-12 16:09:18

+0

他也可以添加Thread.Sleep(100); .DoEvents()之後,但它更加髒:/ – 2012-03-12 16:51:19

0

創建兩個名爲'StopTimer'和'TimerStopped'的布爾值,並將它們的初始狀態設置爲false。將計時器的AutoReset屬性設置爲false。然後經過格式化的方法爲以下幾點:

Invoke((MethodInvoker)delegate { 
    // Work to do here. 
}); 
if (!StopTimer) 
    timer.Start(); 
else 
    TimerStopped = true; 

這樣,你是防止競爭條件,檢查是否計時器應繼續當方法已經達到其最終報告。

現在你們的FormClosing方法是:

if (!TimerStopped) 
{ 
    StopTimer = true; 
    Thread waiter = new Thread(new ThreadStart(delegate { 
     while (!TimerStopped) { } 
     Invoke((MethodInvoker)delegate { Close(); }); 
    })); 
    waiter.Start(); 
    e.Cancel = true; 
} 
else 
    timer.Dispose(); 

如果計時器還沒有停止,一個線程啓動等待,直到它已經這樣做了,然後嘗試再次關閉的形式。

相關問題