Прерывание потока из события таймера

У меня есть Timer, который должен отменить Thread, если для этого потребуется слишком много времени.

System.Timers.Timer timer_timers = new System.Timers.Timer();
Thread thread = new Thread(startJob);
thread.Name = "VICTIM_THREAD";

при запуске метода Thread я запускаю Timer и передаю текущий поток событию в качестве параметра.

public void startJob()
{
    Debug.WriteLine("Name: " + Thread.CurrentThread.Name);
    timer_timers.Elapsed += (sender, e) => T_Elapsed(sender, e, Thread.CurrentThread);
    timer_timers.Interval = 5000;

    // Start simulation process
    while (true)
    {
        Thread.Sleep(700);
        Debug.WriteLine("Thread: " + Thread.CurrentThread.Name + " ALIVE: " + thread.IsAlive);
    }            
}

Событие таймера:

private void T_Elapsed(object sender, ElapsedEventArgs e, Thread currentThread)
{
    // EDIT: show the correct NAME! of the thread
    Debug.WriteLine("Name: " + currentThread.Name);

    System.Timers.Timer tim = sender as System.Timers.Timer;

    currentThread.Abort();  // <-- this line throws exception

    if (tim != null)
    {
        tim.Stop();
    }

}

Но вызов Abort вызывает исключение:

«Невозможно оценить выражение, потому что код оптимизирован или собственный фрейм находится на вершине стека вызовов»

и нить остается живой. Если я запускаю таймер до startJob() и передаю поток напрямую, он работает нормально.

public void startThread()
{
    timer_timers.Elapsed += (sender, e) => T_Elapsed(sender, e, thread);
    timer_timers.Interval = 5000;

    timer_timers.Start();
    thread.Start();
}
public void startJob()
{
    // Start simulation process
    while (true)
    {
        Thread.Sleep(700);
        Debug.WriteLine("Thread: " + Thread.CurrentThread.Name + " ALIVE: " + thread.IsAlive);
    }            
}

Вопрос: Почему не работает Thread.CurrentThread версия? это потому, что мне также пришлось бы прервать поток таймера? Что мне здесь не хватает?

Я нашел ответы на это исключение, например this и this взяты из другого контекста и не очень помогают мне понять, почему.

РЕДАКТИРОВАТЬ: Я знаю, что это неправильный способ прервать или отменить поток. Его задача - открыть SerialPort. Но раз в ~ 200-й раз поток просто никогда не вернется, и мне нужно его убить, не говоря уже о последствиях. имитация цикла while может быть плохим примером.


person Mong Zhu    schedule 07.07.2016    source источник
comment
Никогда не вызывайте Thread.Abort(), так как он может оставить домен приложения в неопределенном состоянии. Единственное исключение из этого - если вы пытаетесь принудительно завершить работу приложения, и в этом случае вас не волнует неопределенное состояние.   -  person Enigmativity    schedule 07.07.2016
comment
Другими словами, выбранный вами подход - неправильный способ завершить поток, и делать это таким образом опасно.   -  person Enigmativity    schedule 07.07.2016
comment
Да, лучший шаблон - использовать CancellationToken для реализации совместного завершения потока. Не могу ответить, почему он так себя ведет, но известно, что он имеет серьезные побочные эффекты. (править) о, вы не захватываете поток, который, как вы думаете, вы используете с Thread.CurrentThread. Ответ Dark Falcon правильный.   -  person    schedule 07.07.2016
comment
И если вы ДЕЙСТВИТЕЛЬНО хотите, чтобы ваше приложение закрылось немедленно, позвоните Environment.FailFast(). Другими словами, нет обстоятельств, при которых вы должны звонить Thread.Abort().   -  person Matthew Watson    schedule 07.07.2016
comment
Вы пытались вызвать Dispose на экземпляре SerialPort из другого потока, чтобы разблокировать его?   -  person spender    schedule 07.07.2016
comment
@spender нет, у меня нет. Я только начал пробовать это решение. Получил эту ошибку, и теперь я пытаюсь понять, почему появляется эта ошибка.   -  person Mong Zhu    schedule 07.07.2016
comment
Спасибо, ребята, за поддерживающие комментарии, я читал это бесчисленное количество раз. Я все еще хотел бы понять основы этого явления.   -  person Mong Zhu    schedule 07.07.2016
comment
@spender хороший момент, я разберусь. Вероятно, это станет следующим вопросом :)   -  person Mong Zhu    schedule 07.07.2016
comment
@spender, вызывающий Dispose, к сожалению, не работает, но хорошая идея. прерывание потоков тоже не помогает. Остается застрять в port.Open();. Я нашел еще пару сообщений. Старые посты ... интересная проблема.   -  person Mong Zhu    schedule 08.07.2016


Ответы (2)


Как отмечено в комментариях, вы не должны использовать Abort. Даже если вы это сделаете, вот проблема в том, как вы его используете:

Таймеры не работают в вашем потоке. Они работают в потоке пула потоков. Следовательно, Thread.CurrentThread, используемый в вашей лямбде, будет этим потоком пула потоков.

Вот что вам следует сделать, если вы хотите прервать поток, создающий таймер: Захватите поток в переменной вне лямбда.

Thread myThread = Thread.CurrentThread;
timer_timers.Elapsed += (sender, e) => T_Elapsed(sender, e, myThread);

Но вы должны найти другой способ более изящно завершить поток или переработать свой код, чтобы не требовать явной потоковой передачи.

person Dark Falcon    schedule 07.07.2016
comment
в строке: Debug.WriteLine("Name: " + Thread.CurrentThread.Name); в методе startJob и в Elapsed отображается правильное имя = VICTIM_THREAD. получает ли поток пула потоков то же имя? - person Mong Zhu; 07.07.2016
comment
Нет, у него не было бы того же имени. Я не верю вашим результатам теста из-за документации, в которой написано: Предоставляет механизм для выполнения метода в потоке пула потоков с заданными интервалами. Я думаю, вы как-то неправильно тестировали. - person Dark Falcon; 07.07.2016
comment
это теоретическое неверие в мои результаты тестов или вы скомпилировали и запустили мой опубликованный код? - person Mong Zhu; 07.07.2016
comment
Он основан на документации (на которую я ссылаюсь) и на БОЛЬШОМ опыте (это моя ежедневная работа) с .NET. У меня нет времени компилировать и запускать каждый бит кода. - person Dark Falcon; 07.07.2016
comment
звучит разумно, я совершенно уверен, что где-то ошибся, мне просто очень хотелось бы знать где. :) - person Mong Zhu; 07.07.2016
comment
эй, моя ошибка, нашла в прошедшем событии не показывает имя! извините за турбулентность - person Mong Zhu; 07.07.2016
comment
Спасибо, красивое решение, оно немного напоминает мне проблему закрытия. Таким образом, в основном путем захвата потока он приклеивается к новой переменной и не будет направлен на неправильный путь через Thread.CurrentThread при срабатывании события таймера? =! я правильно понял? - person Mong Zhu; 07.07.2016
comment
Да это верно. Довольно распространенной практикой является захват некоторого аспекта состояния во внешней области видимости. Вы также можете увидеть это в Javascript, где значение this в обратном вызове отличается, и, таким образом, this должен быть зафиксирован в переменной во внешней области, чтобы его можно было использовать в лямбда-выражении обратного вызова. - person Dark Falcon; 07.07.2016
comment
еще раз извините за взволнованный ответ. и спасибо за урок (тоже из скромности), вы действительно расширили мое понимание. Мне следует потратить больше времени на чтение документации и отвлечься от явной потоковой передачи. Я знаю это. - person Mong Zhu; 07.07.2016

Никогда не звоните Thread.Abort. Вот как это сделать правильно:

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

var t = Task.Run(() =>
{
    while (!token.IsCancellationRequested)
    {
        Console.Write(".");
        Thread.Sleep(500);
    }
}, token);

var timer = new System.Timers.Timer();
timer.Interval = 5000;
timer.Elapsed += (s, e) => tokenSource.Cancel();
timer.Enabled = true;

Причина, по которой ваш код работает во втором случае, заключается в том, что вы захватываете поток перед вызовом T_Elapsed. В первом случае вы запрашиваете текущий поток только тогда, когда вызывается событие time Elapsed (и в этот момент это не вызывающий поток, это вызываемый объект).

person Enigmativity    schedule 07.07.2016
comment
спасибо, но это не совсем ответ на вопрос, почему он работает во втором случае, а не в первом ... - person Mong Zhu; 07.07.2016
comment
@Enigmativity, Тоже наряднее будет если вместо Thread.Sleep (500); используйте token.WaitHandle.WaitOne (500) - person Artavazd Balayan; 07.07.2016
comment
@MongZhu - я добавил ответ. - person Enigmativity; 07.07.2016
comment
спасибо, это действительно добавило мне понимания. особенно в сочетании с ответом Темного Сокола. - person Mong Zhu; 07.07.2016