Help to settle the flow. I will not provide the code, I just need an idea.

There is some event that triggers a thread. I need to make every launch of this thread complete the execution of the previous instance.

Framework 4.0 (without async / await)

  • save the running thread and just abort it at the event :) - Grundy
  • Stop the stream and restart it - not? - Vladimir Martyanov
  • 2
    depending on what the thread itself does, if you can just interrupt it without consequences for the data with which it works, then the Grundy option. If you need to send a command to the correct completion, you can dig into the Task class and CancellationToken - rdorn
  • @ Vladimir Martiyanov, yes, only there are several ways to do this, and those that I tried failed me for unknown reasons. Just hung on waiting for the thread to finish when starting the cancel mechanism. So I decided not to reinvent the wheel but ride on yours, if you do not mind, of course) - iRumba
  • @iRumba Well, here I can only say one thing: TerminateThread (), if you, of course, have a real thread - Vladimir Martyanov

2 answers 2

The “old” and most clumsy way is to create a stream and kill it if necessary. However, potentially this is a very dangerous way, because, for example, a stream can leave general data in an inconsistent state or refuse to complete at all.

 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"); } } 

It is better not to contact Abort() , and use the "soft" thread termination using ManualResetEventSlim , which will serve as a completion flag. This method has one drawback - if you run a synchronous code that can respond for a long time (for example, reading a file or a request to the network), you will have to wait a long time for the stream to complete upon a new launch.

 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, then TPL is available to you. In this case, it will be better to use Task and CancellationToken , with them you can make a "soft" task stop. The advantage of the new model is that almost all asynchronous methods support stopping using CancellationToken , so the code will respond to completion more quickly.

 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 checking tokens:

 try { ... token.ThrowIfCancellationRequested(); ... } catch (OperationCanceledException e) { if (e.CancellationToken != token) { // отмена была вызвана не нами, бросаем исключение throw; } } 
  • I understand correctly that if we in a stream call a "foreign" method that has no idea about our tokens and threads, then it can be interrupted only by the first method? - rdorn
  • one
    I will add that CancellationToken can be used not only with tasks, but also with threads. - Pavel Mayorov
  • one
    @rdorn if we are talking about a plugin, it is better to first send a "soft" request to end, and after the expiration of the timeout, unload the AppDomain. - Pavel Mayorov
  • one
    @PavelMayorov is understandable, and I’m doing it for my plugins. The problem with strangers. In the interface, you cannot prescribe the requirement to correctly handle the token, exceptions and not to try to hang, the maximum presence of a property with a token that can be easily ignored, well, there it is. Even the Stop () method is provided by the interface, but nothing interferes with the story of a dummy on it. - rdorn
  • one
    @rdorn, that's why I’m writing: "unload the AppDomain after the timeout expires" . Especially for those plugins that have a dummy hung on Stop() . - Pavel Mayorov

In the end, I came up with my own solution. If it is developed a little, it can be adapted for other tasks. But this functionality is enough for me.

 public class TaskQueue { Task _worker; Queue<Action> _queue; int _maxTasks; bool _deleteOld; object _lock = new object(); public TaskQueue(int maxTasks, bool deleteOld = true) { if (maxTasks < 1) throw new ArgumentException("TaskQueue: максимальное число задач должно быть больше 0"); _maxTasks = maxTasks; _deleteOld = deleteOld; _queue = new Queue<Action>(maxTasks); } public bool Add(Action action) { if (_queue.Count() < _maxTasks) { _queue.Enqueue(action); DoWorkAsync(); return true; } if (_deleteOld) { _queue.Dequeue(); return Add(action); } return false; } void DoWorkAsync() { if(_queue.Count>0) _worker = Task.Factory.StartNew(DoWork); } void DoWork() { lock (_lock) { if (_queue.Count > 0) { _queue.Dequeue().Invoke(); DoWork(); } } } } 

Everything is simple here. There is a queue of tasks of a certain size and a flag that says whether to delete old tasks in the event of a queue overflow. That is, I add the task, it tries to add to the queue. If there is free space in the queue, it is added, if not, then it is either not added, or it removes the old task (which is next to be executed) from the queue. Next, a queue handler is started, which, if another task is not executed, takes the task from the queue and starts it. Upon completion, it runs recursively.

  • There are a lot of code questions. The type is not thread safe. It is not clear why we need the second Task.Factory.StartNew() call in DoWork() . Calling someone else's code inside a lock can be a problem. Etc. - andreycha
  • @andreycha, I think, from the question it was clear how much I rummaged in multithreading. So what does “non-thread safe” mean? What does it threaten with? Preferably an example in which an artifact or an exception can crash. At least in words. Task.Factory.StartNew(_queue.Dequeue()) in DoWork() is the actual launch of the Action itself, which the programmer passed to it. If you have not noticed, in the Add parameters is not Task , but Action . What problems can result in calling someone else's code inside a lock? Need specifics. - iRumba
  • @andreycha, yes, you are right about StartNew in DoWork . Corrected. - iRumba
  • If you need an asynchronous queue, then it is better to use BufferBlock msdn.microsoft.com/ru-ru/library/hh873173(v=vs.110).aspx - Serginio
  • @Serginio, I wrote Framework 4.0 (без async / await) - iRumba