Подтвердить что ты не робот

Решение проблем "Не удается получить доступ к удаленному объекту". исключение

В моем текущем проекте есть класс 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 - что не работает для меня. Поэтому я думаю, что я делаю что-то неправильно.

Итак, мой вопрос: Почему вышеописанный код запускает исключение, хотя я проверяю, находятся ли объекты, к которым я обращаюсь, или нет?

4b9b3361

Ответ 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();

Если таймер еще не остановлен, запускается поток, чтобы подождать, пока он это сделает, а затем попытайтесь снова закрыть форму.