2016-10-04 2 views
1

Я не могу убить свою нить на C#. Кажется, что программа застревает в бесконечном цикле события FormClosing.Прекращение/соединение нити в C#

EDIT // Я пытаюсь закончить поток и закрыть всю программу при запуске события FormClosing.

Вот код:

public partial class Form1 : Form 
{ 
    private Thread thread; 
    private volatile bool threadRunning = true; 

    public Form1() 
    { 
     InitializeComponent(); 
    } 

    private void Loop() 
    { 
     Console.WriteLine(threadRunning); 
     while (threadRunning) 
     { 
      MethodInvoker mi = delegate { timeLabel.Text = TimeWriterSingleton.Instance.OutputTime(); }; 
      Invoke(mi); 
     } 
    } 

    private void Form1_Load(object sender, EventArgs e) 
    { 
     thread = new Thread(Loop); 
     thread.Start(); 
    } 

    private void Form1_FormClosing(object sender, FormClosingEventArgs e) 
    { 
     threadRunning = false; 
     thread.Join(); 
    } 
} 
+5

Просто не делайте этого.Если вы хотите, чтобы некоторый элемент пользовательского интерфейса был обновлен в будущем, ** сделайте таймер **, и когда отметит таймер, обновите элемент. –

+0

Winforms и WPF не являются потокобезопасными !, поэтому НИКОГДА не вызывайте их из любого потока, кроме потока, который запускает цикл сообщений. –

+0

См. Также, например. https://stackoverflow.com/questions/17575673/thread-join-causing-deadlock, https://stackoverflow.com/questions/12502229/asynchronously-raised-events-which-use-invoke-causing-problems-with- multithreadi и https://stackoverflow.com/questions/24211934/deadlock-when-thread-uses-dispatcher-and-the-main-thread-is-waiting-for-thread-t (последний из них касается WPF, но основная проблема и понятия идентичны). –

ответ

0

Я решил переключиться на использование таймера. Код теперь выглядит так, и приложение работает:

public partial class Form1 : Form 
{ 
    private System.Timers.Timer timer; 

    public Form1() 
    { 
     InitializeComponent(); 

     timer = new System.Timers.Timer(60000); 
    } 

    private void Form1_Load(object sender, EventArgs e) 
    { 
     timeLabel.Text = TimeWriterSingleton.Instance.OutputTime(); 
     timer.Elapsed += TimerElapsed; 
     timer.Enabled = true; 
    } 

    private void TimerElapsed(object sender, ElapsedEventArgs e) 
    { 
     timeLabel.Text = TimeWriterSingleton.Instance.OutputTime(); 
    } 
} 
+1

Следует отметить, что значение в конструкторе таймера ('60000') находится в миллисекундах. Так что будет работать один раз в минуту. И это вряд ли когда-либо будет согласовано с системными часами. – Theraot

-1

Я собирался предложить нечто более сложное, но то, что вы делаете, это довольно просто, так вот простой ответ.

Вместо темы Thread просто используйте поток ThreadPool, чтобы выполнить свою работу и избавиться от этого thread.Join() в вашем закрытии формы.

Нити ThreadPool являются фоновыми потоками и автоматически уничтожаются, когда приложение закрывается.

Ваша проблема заключается в том, что вы пытаетесь Invoke методом на потоке пользовательского интерфейса, но когда вы не выходя thread.Join() эффективно блокирует поток пользовательского интерфейса до ваших выходов резьбы рабочих. Итак, ты ДОЗЫВАЙТЕ себя. Добро пожаловать в мир многопоточности.

Кроме того, если вы обнаружите, что используете Thread, вы, вероятно, делаете что-то неправильно. Вы должны использовать Tasks. Это проще, и с помощью токенов отмены вы можете участвовать в совместном аннулировании.

+0

Не похоже, что вы не можете использовать токены отмены с фактическими потоками. И задачи не предназначены для работы с длительными операциями, поскольку вы получаете только ограниченное количество и не гарантируете, что кто-нибудь из них когда-либо начнет. Я бы не стал распускать темы так же быстро, как и вы. – Blindy

+0

Маркировочные жетоны сплетены во всех современных асинхронных операционных системах, поэтому это не совсем то же самое, что, например, использование autoresetevent для обозначения других потоков. Я бы хотел увидеть цитату для «ограниченного количества», и когда планировщик по умолчанию решает никогда не запускать вашу задачу. OP определенно не нуждается в потоке, и люди, которые не знают, как правильно использовать потоки, должны придерживаться более совершенных многопоточных фреймворков. – Will

0

Ваш Join заблокировал поток GUI, а ваш Invoke в другом потоке ждет, пока ваш поток GUI обработает делегат.

Быстрое исправление будет состоять в том, чтобы использовать BeginInvoke вместо Invoke, таким образом отправляя вместо отправки оконного сообщения.

В качестве альтернативы, не присоединяйтесь. Цель этого кода состоит в том, чтобы очистить себя, почему вы заботитесь, когда нить умирает?

Третьим исправить было бы просто потянуть нить, либо через Thread.Abort, либо Environment.Exit. Он может пропустить некоторую очистку, но ваш конкретный код не должен заботиться, и в любом случае нужно выйти.

Edit: рабочий код, используя BeginInvoke следующим образом:

private void Loop() 
    { 
     while (threadRunning) 
     { 
      BeginInvoke(new MethodInvoker(() => timeLabel.Text = DateTime.Now.ToString())); 
      Thread.Sleep(100); 
     } 
    } 

    private void Form1_FormClosing(object sender, FormClosingEventArgs e) 
    { 
     threadRunning = false; 
     thread.Join(); 
    } 

Проблема с исходным кодом является то, что он работает так же быстро, как ваш процессор позволяет, наполняя очередь сообщений в точке, где GUI поток не может поддерживать. Обновление элементов управления Windows очень дорого, по сравнению с простое добавление числа в очередь. Поэтому я добавил паузу между обновлениями пользовательского интерфейса, чтобы дышать потоком графического интерфейса.

Для спутников, мне было бы любопытно, почему вы это делаете. Ничто из того, что я сказал, фактически неверно.

+0

Begininvoke не позволяет закрыть форму. Он работает навсегда – Naidu

+0

Другие альтернативы могут работать. Но я пытаюсь исправить его конкретный сценарий. Это пример состояния гонки. Мой предыдущий ответ (который я удалил), работал в первый раз, но не оттуда. Я удалил его. – Naidu

+0

Это не состояние гонки, а 'BeginInvoke' работает отлично. Я предлагаю очистить многопоточность .NET перед дальнейшими комментариями. – Blindy

0

Фактически использование BeginInvoke() - неплохая идея. Это может выглядеть так:

private void Form1_Load(object sender, EventArgs e) 
{ 
    thread = new Thread(() => Loop(this)); 
    thread.Start(); 
} 

private void Loop(Form1 form) 
{ 
    while (threadRunning && !form.IsDisposed) 
    { 
     MethodInvoker mi = delegate() { timeLabel.Text = /* Some text */ ; }; 
     BeginInvoke(mi); 

     // Let sleep some time... 
     Thread.Sleep(1); 
    } 
} 

private void Form1_FormClosing_1(object sender, FormClosingEventArgs e) 
{ 
    threadRunning = false; 
    thread.Join(); 
} 
+0

_bool threadRunning_ и _Sleep() _ можно комбинировать с одним ** ManualResetEvent **. – Jackdaw

Смежные вопросы