У меня есть 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);
}
Taskесть свои шаблоны проектирования, которые сильно отличаются от того, как работали с потоками когда-то в древности :) – CrazyElf Mar 18 '21 at 13:01FactoryTask?.StartNewзаменить просто наTask.Run, он будет использовать правильный шедьюлер для выполнения на потоке из пула, так что его не надо будет задавать.CancellationTokenSourceявляетсяIDisposable, ему надо высвобождать ресуры, нужен либоusingлибоDispose(), посмотрите ссылку в моем ответе на пример. При использованииasync voidесть риск словить невидимое исключение, то есть оно уронит вам поток, но вы его не увидите, оберните код вtry-catchи почитайте в статье по ссылке из ответа про асинхронность, чем опасенasync void. – aepot Mar 19 '21 at 15:03Dispatcher.Invoke(new Action(() => { }))можно упростить какDispatcher.Invoke(() => { }). Кстати, проasync voidмного всякого понаписано, поищите, почитайте, вам это пригодится. – aepot Mar 19 '21 at 15:04m_exeTask.Dispose();- а вот это лишнее. Если вы не используетеTask.WaitHandleявно (а вы не используете), то таск не нужно диспозить, хуже оно конечно не делает, но и лучше тоже. – aepot Mar 19 '21 at 15:10