Как остановить поток C#
У меня есть thread с циклом на 15 элементов и для эмитации работы я делаю sleep на секунду. Я сделала логику которая если юзер нажимает на кнопку повторно, то предыдущий поток должен быть остановлен и новый должен начать работу. Для этого я создала флаг m_isShouldStop и когда юзер повторно нажимает кнопку, то флаг меняется на true и следом вызываю Wait и ожидаю, что предыдущий поток остановиться, дождется Wait, создаст новый поток и начнет выполнение заново. Но происходит следующее - я вижу, что флаг меняется, но в потоке флаг как будто остается тем же(как будто работает с его копией) и Wait не выходит он просто зависает там.
Вот код:
private Task m_exeTask;
private static bool m_isShouldStop = false;
private TaskFactory FactoryTask { get; set; }
public void Start(int count)
{
if (m_exeTask != null)
{
m_isShouldStop = true;
m_exeTask.Wait();
m_exeTask.Dispose();
m_exeTask = null;
m_isShouldStop = false;
}
var uiContext = TaskScheduler.FromCurrentSynchronizationContext();
m_exeTask = FactoryTask?.StartNew(() =>
{
if(m_isShouldStop ){
return;
}
Console.WriteLine("HERE start the loop");
for (int i = 0; i < count; i++)
{
Thread.Sleep(1000); //WORK EMULATION
Dispatcher.Invoke(new Action(() =>
{
//Here I do some UI stuff
Console.WriteLine("HERE start NEXT");
}), DispatcherPriority.ContextIdle);
}
Console.WriteLine("HERE end the loop");
}, tokenSource.Token, TaskCreationOptions.None, uiContext);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Start(15);
}
}
Что делаю не так?
ПРАВКА в итоге получилось так, код работает. Если есть рекомендации, пишите :-)
CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
public async void Start(int count)
{
if (m_exeTask != null)
{
cancelTokenSource.Cancel();
await m_exeTask;
m_exeTask.Dispose();
m_exeTask = null;
cancelTokenSource = new CancellationTokenSource();
}
var uiContext = TaskScheduler.FromCurrentSynchronizationContext();
m_exeTask = FactoryTask?.StartNew(() =>
{
Console.WriteLine("HERE start the loop");
for (int i = 0; i < count; i++)
{
if (cancelTokenSource.Token.IsCancellationRequested)
{
Console.WriteLine("IT WAS CANCELED");
break;
}
else
{
Thread.Sleep(1000); //WORK EMULATION
Dispatcher.Invoke(new Action(() =>
{
//Here I do some UI stuff
Console.WriteLine("HERE start NEXT");
}), DispatcherPriority.ContextIdle);
}
}
Console.WriteLine("HERE end the loop");
}, tokenSource.Token, TaskCreationOptions.None, uiContext);
}
Ответы (1 шт):
Представьте себе ситуацию, когда вы вызвали Wait(), при этом поток еще продолжает выполняться. Сам Wait() уже заблокировал UI поток и он ждет.
Далее, в выполняемом потоке у вас вызывается Dispatcher.Invoke, то есть выполнение фрагмента кода в UI потоке, но он уже выполняет вот прямо сейчас Wait(), то есть заблокирован, при этом ваш поток ждет, когда Dispatcher.Invoke отработает, а Dispatcher.Invoke ждет, когда отработает Wait(), чтобы выполнить отправленный в него фрагмент кода.
И получается ситуация, когда UI поток ждет завершения сторого потока, а второй поток не может завершиться, пока UI поток не выполнит для него код. И вот они ждут друг друга...и ждут...и будут вечно ждать, пока вы не убьете приложение.
И такая смертельная для приложения взаимоблокировка потоков называется дэдлок - Deadlock.
Вылечить можно любым из способов:
- Выполнить
Console.WriteLine()прямо в потоке без вызова диспетчера - Вызвать диспетчер асинхронно, то есть
BeginInvokeвместоInvoke, разница в том, что первый не будет ждать, пока отработает код в UI потоке - Ожидать завершения задачи асинхронно (не блокируя UI поток), то есть
await m_exeTaskвместоm_exeTask.Wait()- Асинхронное программирование
Добавлю только что вместо bool, которая не является потокобезопасной, ибо может быть кеширована в потоке, надежнее использовать volatile bool, а еще лучше - познакомиться с тем, как используется CancellationToken (вот пример его использования).
Так же проверку на выход стоит делать прямо в цикле
for (int i = 0; i < count; i++)
{
if (m_isShouldStop)
return;
// ...
}