Помогите разрулить потоки. Я не буду предоставлять код, мне нужна просто идея.
Есть некоторое событие, по которому запускается некоторый поток. Мне нужно сделать так, чтобы каждый запуск этого потока завершал выполнение предыдущего экземпляра.
Framework 4.0 (без async / await)
Ответ
"Старый" и самый топорный способ заключается в создании потока и его убийстве при необходимости. Однако потенциально это весьма опасный способ, т.к., например, поток может оставить общие данные в неконсистентном состоянии или вовсе отказаться завершаться
class Program
{
private static Thread _thread;
static void Main(string[] args)
{
Console.ReadLine();
TriggerEventOld();
Console.ReadLine();
TriggerEventOld();
// дождемся завершения
_thread.Join();
}
private static void TriggerEventOld()
{
if (_thread != null)
{
_thread.Abort();
// корректнее будет дождаться завершения
_thread.Join();
}
_thread = new Thread(Foo);
_thread.Start();
}
private static void Foo()
{
Console.WriteLine("Foo started");
try
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(500);
}
}
catch (ThreadAbortException)
{
Console.WriteLine("Foo aborted");
}
Console.WriteLine("Foo ended");
}
}
Лучше не связываться с Abort(), и использовать "мягкое" завершения потока с помощью ManualResetEventSlim, который послужит флагом завершения. У такого способа есть один минус -- если вы запускаете синхронный код, который может долго отвечать (например, чтение файла или запрос в сеть), то придется долго ждать завершения потока при новом запуске.
class Program
{
private static Thread _thread;
private static ManualResetEventSlim _reset = new ManualResetEventSlim();
static void Main(string[] args)
{
Console.ReadLine();
TriggerEventOld();
Console.ReadLine();
TriggerEventOld();
// дождемся завершения
_thread.Join();
}
private static void TriggerEventOld()
{
if (_thread != null)
{
_reset.Set();
// корректнее будет дождаться завершения
_thread.Join();
}
_thread = new Thread(Foo);
_reset.Reset();
_thread.Start();
}
private static void Foo()
{
Console.WriteLine("Foo started");
for (int i = 0; i < 10; i++)
{
if (_reset.IsSet)
{
Console.WriteLine("Foo canceled");
break;
}
Thread.Sleep(500);
}
if (!_reset.IsSet)
{
Console.WriteLine("Foo ended");
}
}
}
Но поскольку вы используете .NET 4.0, значит вам доступен TPL. В таком случае будет лучше воспользоваться Task и CancellationToken, с ними вы можете сделать "мягкую" остановку задачи. Преимущество новой модели заключается в том, что практически все асинхронные методы поддерживают остановку с помощью CancellationToken, поэтому код будет реагировать на завершение быстрее.
class Program
{
private static Task _task;
private static CancellationTokenSource _cts;
static void Main(string[] args)
{
Console.ReadLine();
TriggerEventNew();
Console.ReadLine();
TriggerEventNew();
// дождемся завершения;
// этот вызов может выбросить исключение,
// если внутри возникнет необработанное исключение
_task.Wait();
}
private static void TriggerEventNew()
{
if (_task != null)
{
_cts.Cancel();
// корректнее будет дождаться завершения;
// этот вызов может выбросить исключение,
// если внутри возникнет необработанное исключение
_task.Wait();
}
// пересоздаем каждый раз, поскольку это одноразовый объект
_cts = new CancellationTokenSource();
_task = Task.Factory.StartNew(() => FooNew(_cts.Token), _cts.Token);
}
private static void FooNew(CancellationToken token)
{
Console.WriteLine("FooNew started");
for (int i = 0; i < 10; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("FooNew aborted");
break;
}
Thread.Sleep(500);
}
if (!token.IsCancellationRequested)
{
Console.WriteLine("FooNew ended");
}
}
}
Если будете использовать token.ThrowIfCancellationRequested(), не забудьте обернуть код в блок try/catch c проверкой токена:
try
{
...
token.ThrowIfCancellationRequested();
...
}
catch (OperationCanceledException e)
{
if (e.CancellationToken != token)
{
// отмена была вызвана не нами, бросаем исключение
throw;
}
}