У меня есть код, как показано ниже. Просто запустите цикл while на долгое время.
public void Test(){
var task1 = Task.Run(() =>
{
while(true){
// forever
}
}
}
Когда Test()
вызывается, task1
будет выполнено. Затем, когда я снова запускаю Test()
, одновременно существуют две задачи.
Есть ли способ отменить задачу, которая уже выполнялась, при создании новой задачи?
int i = 0;
private CancellationTokenSource cancellationTokenSource;
public void Test(int b)
{
if (cancellationTokenSource != null)
{
// Cancel previously created task.
cancellationTokenSource.Cancel();
}
cancellationTokenSource = new CancellationTokenSource();
var task1 = Task.Run(() =>
{
while (!cancellationTokenSource.IsCancellationRequested)
{
Console.WriteLine(b);
// forever
}
});
}
public void button_pushed(){
Console.WriteLine("pushed");
Test(i); i++; return;
}
Лог такой:
pushed
0
0
0
0
0
pushed
1
1
1
1
pushed
1
1
1
1
2
2
2
2
2
1
1
1
1
2
2
2
pushed
1
1
1
1
2
2
2
2
2
1
1
1
1
3
3
3
3
2
2
2
....
Похоже, прошлые задачи все еще выполняются. Но когда я выполнил это три раза, первый вывод (0) исчез, так это проблема времени?
То, что я ищу, похоже на:
pushed
0
0
0
0
pushed
1
1
1
1
pushed
2
2
2
2
....
Передайте CancellationToken
методу, затем измените его на while (!CancellationToken.IsCancelationRequested)
.
Вы действительно хотите отменить первый запуск или предпочитаете, чтобы второй вызов не запускал второй? Как есть, кажется произвольным, какой из двух работает, пока работает один.
У меня возникнет соблазн сделать шаг назад и спросить, хорошее ли это решение? Например, может быть, вам лучше использовать BackgroundService?
Вы можете попробовать использовать CancellationTokenSource
. когда метод вызывается (и Task
создается, передаем ему токен для CancellationTokenSource
, который также будет создан для задачи. Перед созданием CancellationTokenSource
нам просто нужно проверить, был ли он создан, и если да, вызвать метод Cancel
.
Простая реализация:
private CancellationTokenSource cancellationTokenSource;
public void Test()
{
if (cancellationTokenSource != null)
{
// Cancel previously created task.
cancellationTokenSource.Cancel();
}
cancellationTokenSource = new();
var task1 = Task.Run(() =>
{
while(!cancellationTokenSource.IsCancellationRequested){
// forever
}
});
}
Конечно, если вы хотите, вы можете отслеживать все CancellationTokenSource
в некоторой коллекции, сделать ее потокобезопасной, используя ConcurrentBag
для их хранения и т. д. Это просто упрощенная идея.
ОБНОВЛЯТЬ
Прочитав ваше обновление с моим решением, я решил дать более подробную реализацию с сохранением всех источников токенов отмены и их правильной отменой:
public class Test
{
int i = 0;
private ConcurrentBag<CancellationTokenSource> cancellationTokenSources = new();
public void Test(int b)
{
var notCancelled = cancellationTokenSources
.Where(c => !c.IsCancellationRequested)
.ToArray();
// Cancel previously created tasks.
Array.ForEach(notCancelled, x => x.Cancel());
var cts = new CancellationTokenSource();
cancellationTokenSources.Add(cts);
var task1 = Task.Run(() =>
{
while (!cts.IsCancellationRequested)
{
Console.WriteLine(b);
// forever
}
Console.WriteLine("FININSHED");
});
}
public void button_pushed()
{
Console.WriteLine("pushed");
Test(i); i++; return;
}
}
Передача Token
задаче только отменяет ее, если она еще не началась, поэтому она не работает с Task.Run
, поскольку запускается немедленно. Вы должны проверить Token
вручную.
while(true){
следует проверить состояние токена, верно? (Не ДВ)
@PoulBak Спасибо, что указали на это
Чтобы уточнить: передача Token
to Task.Run
работает только в том случае, если токен уже отменен, когда вы вызываете Task.Run
. В этом случае Task
никогда не запустится.
Спасибо за ваш ответ. Я попробовал ваш код, но кажется, что прошлые задачи все еще выполняются. Я отредактировал вопрос и включил более подробную информацию. Я был бы рад, если бы вы могли научить меня, когда у вас будет время.
Почему бы не использовать
task1
в качестве поля/свойства вместо локальной переменной?