This question has already been answered:

The application uses backgroundworkers for some tasks. Tell me how to make sure that a certain code fragment is always executed only from the main application flow, even when accessing it from the worker’s thread?

Reported as a duplicate by participants PashaPash , Vladimir Glinskikh , Aries , ermak0ff , aleksandr barakin 4 Oct '15 at 11:19 .

A similar question was asked earlier and an answer has already been received. If the answers provided are not exhaustive, please ask a new question .

  • And what should (or what is expected) occur when accessing such a piece of code from a worker? skip it, block it ... - IAZ
  • This code should be executed from the main thread. - Eugen
  • I will add, very often you need to start the second thread (like a fork) for which you need th.Join (); // transfer control to the stream - nick_n_a

4 answers 4

In order to be able to execute code in some thread, it is necessary that this thread itself be organized in a certain way, namely in the form of a message processing loop. In the case of WinAPI, this is achieved using the GetMessage - DispatchMessage cycle. If you do not use Windows Forms, a similar loop can be organized using the EventWaitHandle class.

private readonly EventWaitHandle m_signal = new EventWaitHandle(false, EventResetMode.AutoReset); private readonly Queue<Action> m_events = new Queue<Action>(); private volatile bool m_stop = false; public void Start() { // Создаём поток синхронизации. Все события будут обрабатываться в нём var th = new Thread(this.Run); th.Start(); } private void Run() { // Ожидаем сигнала о новом событии while (m_signal.WaitOne(Timeout.Infinite)) { // Если пришёл сигнал о прекращении работы - выходим if (m_stop) break; // Пока обрабатываются сообщения из очереди, добавлять их нельзя lock(m_events) { while (m_events.Count > 0) { try { // Достаём из очереди делегат и выполняем его m_events.Dequeue()(); } catch(Exception ex) { // Как-нибудь обрабатываем внешнее исключение } } } } // Освобождаем ресурсы m_signal.Close(); } public void Invoke(Action action) { if (action == null) throw new ArgumentNullException("action"); // Добавляем событие в очередь и инициируем событие // Пока добавляем событие, приостанавливаем обработку lock(m_events) { m_events.Enqueue(action); m_signal.Set(); } } public void Stop() { // Инициируем сигнал о прекращении работы lock(m_events) { m_stop = true; m_signal.Set(); } } 
  • The correctness of this code depends largely on how the class Action is organized and the work with its objects stored in m_events. - IAZ
  • 2
    Action is a standard delegate, with no parameters and no return value. For good, m_events.Dequeue () () should be taken in try-catch, since the fall of any external delegate will cause the entire message processing cycle to collapse, but exception handling will not help to improve the visibility of the example. - Modus
  • I do not know what a "standard delegate" is. I'm not talking about try-catch, but about the fact that the delegates may need synchronization. You have not mentioned it anywhere, and people might think that in any implementation Action will work that way. - IAZ
  • 1. "Standard delegate" - this means a library type representing the signature of the method reference. Accordingly, there can be no talk of any "implementation" of Action - it is just a reference to an arbitrary method of a certain signature. The fact that this is not a class should be obvious by the fact that a construction with two pairs of brackets is used: we get a reference to the method from the queue and immediately call the method using this reference. 2. I did correct the code in accordance with my previous reservation. Now it will work regardless of what method will be passed from the outside. - Modus
  • It is from this approach that hard-to-find bugs appear (put a try-catch — not the fact that it’s the right one — and are satisfied). I repeat once again - the implementation of the methods referred to by the Action must be monitored from the point of view of synchronization. Well, if these methods are not used anywhere else (read - in any other thread), and if at least one of them will be called, then synchronization of these streams may be required. Only this I meant. So the idea is clear? - IAZ

I think it’s worth giving another example, this time how to use the ISyncronizeInvoke interface. This interface is implemented in the System.Windows.Forms.Control class and is designed specifically for pushing any delegates into the message loop processing flow, in the case of controls, the Windows message loop.

The interface contains 4 members.

The InvokeRequired property returns true if the current execution thread is not a message loop processing thread. If we are already in the message loop processing thread, the property returns false.

The Invoke method adds the delegate passed to it to the message processing cycle and blocks the current thread until this delegate executes the message processing cycle. After the delegate is executed, the method returns control with the value that the delegate passed to it returned during execution. If the delegate had a return type of void, the method returns null.

The BeginInvoke method adds the delegate passed to it as a parameter to the message processing loop and returns immediately. It returns an object of type IAsyncResult, which can be passed as a parameter to the last EndInvoke method to wait for the completion of an asynchronous operation. The simplest message loop, which I used in the previous example, handles events asynchronously, returning control, without waiting for the method added to the queue.

In order to add an element to the collection so that the addition itself necessarily takes place in the UI thread, regardless of where it was called from, you should use this pattern:

 private readonly ISynchronizeInvoke m_invoker; private readonly BindingList<MyEntry> m_entries = new BindingList<MyEntry>(); // Конструктор с Dependency Injection, которому передаётся контрол либо форма public BackgroundHandler(ISynchronizeInvoke invoker) { if (invoker == null) throew new ArgumentNullException("invoker"); m_invoker = invoker; } // public bool Add(MyEntry entry) { // Если мы не в потоке обработки сообщений, // метод добавляет в очередь сообщений ссылку на самого себя, // ждёт завершения обработки и возвращает управление if (m_invoker.InvokeRequired) return (bool)invoker.Invoke(new Func<MyEntry, bool>(this.Add), new object[] { entry }); // Поскольку мы сюда попали, мы уже в потоке обработки сообщений, // делаем полезную работу - добавляем переданный элемент в список. // Возвращаемое значение - чисто для демонстрации, как его возвращать m_entries.Add(entry); return true; } 

Placing the synchronization code directly to the beginning of the method before the useful work makes it very easy to read the code if the useful work contains a lot of logic.

    It is impossible to force the execution of code in another thread - you can only transfer control to another thread to execute this (or other) code. This is shown by all the examples given in the answers to the question. A function (or "code section") called in one thread cannot move to another thread. Only the following patterns are possible:

    • suspend execution of a thread in this section of the code until it is executed in another thread (critical sections or semaphores)
    • skipping a section of code for some threads (implemented in different ways)
    • transfer of control to another thread with suspension (or without suspension) of execution of the given thread (events, mutes)

    The examples of Modus are variations of the third item of the list + windows message cycle. But not the answer to the question.

    • I find the answers quite comprehensive and appropriate for my task. PS Why argue and reject the decision of another participant, while arguing on abstract topics? - Eugen

    You, apparently, do not understand how multithreaded applications are executed. For example, there are two streams:

    • 1st thread: F1 () function works, calls F2 ()
    • 2nd thread: G1 () function works, calls F2 ()

    In the “common” function F2 (), some synchronization is required (which depends on the function), but the stream cannot “change” from the 2nd to the 1st. The context is different.

    • I will give an example of what I want: in the worker’s stream, some conditions are fulfilled, and in the case of obtaining the desired result, changes are made to a certain collection. In my opinion, these changes may well be made from the main stream, the context has nothing to do with it. - Eugen
    • In this case, you need the function F2 (), which implies the actions described to you, run it in a new thread and wait for the event (at the event). As soon as your worker installs the event and runs on. And the stream with the F2 () function catches the event and performs its actions. But it is still impossible to transfer to the main thread, unless of course you expect the completion of the worker in it. - IAZ