2012-03-12 19 views
12

W moim obecnym projekcie istnieje klasa postaci, która wygląda następująco:Rozwiązywanie problemów "Nie można uzyskać dostępu do usuniętego obiektu." Wyjątkiem

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))); 
     } 
    } 

} 

Jak widać używam obiektu System.Timers.Timer. Wiem, że mógłbym użyć System.Windows.Forms.Timer, ale jestem dość zainteresowany tym, dlaczego wciąż otrzymuję wyjątek pokazany w tytule. Jest rzucany na wywołanie Invoke w metodzie UpdateDistanceLabel. Co mnie myli, jest to, że mówi "Nie mogę uzyskać dostępu do unieszkodliwionego obiektu: FormMain", mimo że sprawdzam, czy jest on unieszkodliwiony, czy nie. Tak więc nie powinno się zdarzyć. Próbowałem także pozbyć się obiektu timera w zdarzeniu FormClosing, a także nadpisać Dispose (bool) i usunąć go tam, z których oba niestety nie pomogły. Ponadto, wyjątek nie zawsze jest rzucany, podobno tylko wtedy, gdy czas się uruchamia, gdy program się kończy. Nadal wiele się dzieje.

Widziałem, że istnieje mnóstwo wątków na ten temat, ale już próbowałem rozwiązania zamieszczone tam, większość z nich polega na sprawdzeniu właściwości IsDisposed - która nie działa dla mnie. Sądzę, że robię coś złego.

Moje pytanie: Dlaczego powyższy kod wywołuje wyjątek, mimo że sprawdzam, czy obiekty, do których mam dostęp, są usuwane, czy nie?

Odpowiedz

9

Istnieją dwa obejścia: albo połknąć wyjątek i przekleństwo Microsoft dla nie posiadające włączone do TryInvoke i TryBeginInvoke metod, albo wykorzystanie blokowania w celu zapewnienia, że ​​żadne próby Dispose obiektu podczas jego użytkowania, a nie próba jest wykonane w celu użycia obiektu podczas gdy Dispose jest w toku. Myślę, że przełknięcie tego wyjątku jest prawdopodobnie lepsze, ale niektórzy ludzie mają instynktowną reakcję na takie rzeczy, a przy użyciu blokowania można uniknąć sytuacji, w której wyjątek występuje w pierwszej kolejności.

9

Jednym z problemów jest sprawdzanie wątku timera przed wywołaniem Invoke. Istnieje możliwość wyścigu, w którym Formularz może zostać usunięty po sprawdzeniu i przed wykonaniem wywołanej akcji.

Powinieneś wykonać sprawdzanie wewnątrz metody (wyrażenie lambda w twoim przypadku) wywoływane przez Invoke.

Innym możliwym problemem jest dostęp do Cursor.Position w wątku z zegarem. Nie jestem pewien, czy to jest poprawne - zrobiłbym to w głównym wątku. Twój kod zawiera także komentarz //some code - więc prawdopodobnie pominąłeś kod, który również musisz sprawdzić.

Ogólnie rzecz biorąc, prawdopodobnie lepiej byłoby użyć numeru System.Windows.Forms.Timer.

+0

Dzięki. Tak właśnie zrobiłem, ale nie wpłynęło to bardzo na zachowanie. – haiyyu

6

Oto moje rozwiązanie do wyjątku, jeśli jesteś zainteresowany:

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

.Zatrzymaj() bez .DoEvents() nie wystarczy, jak to będzie wyrzucać przedmiotów, nie czekając na zakończenie jego wątku praca.

+0

Tak, szybkie i brudne, ale to wystarczy. I pokazuje, że jest przyczyną. –

+0

Wywołanie "Application.DoEvents" nie gwarantuje, że unikniesz potencjalnego stanu wyścigu, choć prawdopodobnie spowoduje to, że będzie on mniej prawdopodobny. – Joe

+0

Może także dodać Thread.Sleep (100); po .DoEvents() jest jednak jeszcze bardziej brudny:/ –

0

Utwórz dwa bajty o nazwie "StopTimer" i "TimerStopped", przy czym ich początkowe stany mają wartość false. Ustaw właściwość AutoReset licznika czasu na wartość false. Następnie sformatować dotychczasowy sposób, że:

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

ten sposób uniemożliwiają sytuacji wyścigu, sprawdzenie czy licznik powinien kontynuować i raportowania, gdy metoda osiągnęła swój koniec.

Teraz ustaw metodę FormClosing do tego:

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(); 

Jeśli zegar nie został jeszcze zatrzymany, wątek jest uruchomiona czekać aż tak zrobiła, a następnie spróbuj ponownie zamknąć formularz.

Powiązane problemy