Question:
Help to smooth out the flows. I will not provide the code, I just need an idea.
There is some event that triggers some thread. I need to make it so that every run of this thread terminates the execution of the previous instance.
Framework 4.0 (no async / await)
Answer:
The "old" and most clumsy way is to create a stream and kill it if necessary. However, this is potentially a very dangerous method, because, for example, a thread can leave shared data in an inconsistent state or refuse to terminate altogether.
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");
}
}
Better not to mess with Abort()
, and use a "soft" thread termination using ManualResetEventSlim
, which will serve as the termination flag. This method has one drawback – if you run synchronous code that can take a long time to respond (for example, reading a file or making a request to the network), you will have to wait a long time for the thread to complete when it is restarted.
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");
}
}
}
But since you are using .NET 4.0, the TPL is available to you. In this case, it would be better to use Task
and CancellationToken
, with them you can make a "soft" stop of the task. The advantage of the new model is that almost all asynchronous methods support stopping with a CancellationToken
, so your code will respond to completion faster.
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");
}
}
}
If you use token.ThrowIfCancellationRequested()
, do not forget to wrap the code in a try/catch
with token validation:
try
{
...
token.ThrowIfCancellationRequested();
...
}
catch (OperationCanceledException e)
{
if (e.CancellationToken != token)
{
// отмена была вызвана не нами, бросаем исключение
throw;
}
}