Решение проблем "Не удается получить доступ к удаленному объекту". исключение
В моем текущем проекте есть класс Form, который выглядит так:
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, но меня довольно интересует причина, по которой я все еще получаю исключение, указанное в названии. Он получает вызов Invoke в методе UpdateDistanceLabel. Меня смущает то, что в нем говорится: "Не удается получить доступ к удаленному объекту: FormMain", даже если я проверяю, выбрано оно или нет. Так что этого не должно быть. Я также попытался удалить объект таймера в событии FormClosing, а также переопределить Dispose (bool) и удалить его там, оба из которых, к сожалению, вообще не помогли. Кроме того, исключение не всегда получается брошенным, предположительно, только когда таймер срабатывает, пока программа выходит. Это все еще случается много.
Я видел, что в этом есть много потоков, но я уже пробовал решения, размещенные там, большинство из которых связано с проверкой свойства IsDisposed - что не работает для меня. Поэтому я думаю, что я делаю что-то неправильно.
Итак, мой вопрос:
Почему вышеописанный код запускает исключение, хотя я проверяю, находятся ли объекты, к которым я обращаюсь, или нет?
Ответы
Ответ 1
Существует два способа обхода: либо проглатывать исключение, либо проклинать Microsoft за то, что он не включил методы TryInvoke
и TryBeginInvoke
, а также использовать блокировку для обеспечения того, чтобы объект Dispose
не выполнялся во время использования, и не предпринимается попытка использовать объект, пока выполняется Dispose
. Я думаю, что проглатывание исключения, вероятно, лучше, но некоторые люди имеют висцеральную реакцию на подобные вещи, и, используя блокировку, можно избежать возникновения исключения в первую очередь.
Ответ 2
Одна из проблем заключается в том, что вы выполняете проверку потока таймера перед вызовом Invoke
. Существует возможное состояние гонки, когда Форма может быть удалена после проверки и до того, как выполнено действие, вызванное.
Вы должны делать проверку внутри метода (выражение lambda в вашем случае), вызываемое Invoke
.
Другая возможная проблема заключается в том, что вы обращаетесь к Cursor.Position
по потоку таймера. Я не уверен, что это действительно так - я бы сделал это в основном потоке. Ваш код также включает комментарий //some code
- поэтому вы, вероятно, пропустили какой-то код, который вам также нужно проверить.
В целом, возможно, вам лучше использовать System.Windows.Forms.Timer
.
Ответ 3
Вот мое решение для вашего исключения, если вы заинтересованы:
private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
{
timer.Stop();
Application.DoEvents();
}
.Stop() без .DoEvents() недостаточно, так как он будет размещать объекты, не дожидаясь завершения работы вашего потока.
Ответ 4
Создайте два булеана, называемых "StopTimer" и "TimerStopped", причем их начальные состояния установлены в false. Установите для свойства AutoReset таймера значение false. Затем отформатируйте метод Elapsed следующим образом:
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();
Если таймер еще не остановлен, запускается поток, чтобы подождать, пока он это сделает, а затем попытайтесь снова закрыть форму.