Уведомлять, когда поток завершен, без блокировки вызывающей нити

Я работаю над устаревшим приложением, которое построено поверх NET 3.5. Это ограничение, которое я не могу изменить. Мне нужно выполнить второй поток, чтобы выполнить долговременную задачу без блокировки пользовательского интерфейса. Когда поток завершен, мне нужно выполнить обратный вызов.

Сейчас я попробовал этот псевдокод:

Thread _thread = new Thread(myLongRunningTask) { IsBackground = True };
_tread.Start();
// wait until it done
_thread.Join();
// execute finalizer

Второй вариант, который не блокирует пользовательский интерфейс, выглядит следующим образом:

Thread _thread = new Thread(myLongRunningTask) { IsBackground = True };
_tread.Start();
// wait until it done
while(_thread.IsAlive)
{
    Application.DoEvents();
    Thread.Sleep(100);
}
// execute finalizer

Конечно, второе решение не является хорошим поводом для перезарядки пользовательского интерфейса. Каков правильный способ выполнения обратного вызова при завершении _thread? Также, как узнать, был ли поток отменен или прерван?

* Примечание: * Я не могу использовать BackgroundWorker, и я не могу использовать библиотеку Async, мне нужно работать с классом родного потока.

Ответ 1

Самый простой способ - добавить обратный вызов, в основном. Вы даже можете сделать это, просто используя способ работы делегатов многоадресной рассылки:

ThreadStart starter = myLongRunningTask;
starter += () => {
    // Do what you want in the callback
};
Thread thread = new Thread(starter) { IsBackground = true };
thread.Start();

Это очень ваниль, и обратный вызов не будет запущен, если поток прерывается или генерирует исключение. Вы можете обернуть его в классе с помощью нескольких обратных вызовов или обратного вызова, который определяет статус (прерван, генерирует исключение и т.д.) И обрабатывает его, обертывая исходный делегат, вызывая его в методе с помощью try/catch блокировать и выполнять обратный вызов соответствующим образом.

Если вы не предпримете каких-либо специальных действий, обратный вызов будет выполнен в фоновом потоке, поэтому вам нужно будет использовать Control.BeginInvoke (или что-то еще) для возврата к потоку пользовательского интерфейса.

Ответ 2

Я абсолютно понимаю ваши требования, но вы пропустили одну важную вещь: вам действительно нужно ждать окончания этого потока синхронно? Или, может быть, вам просто нужно выполнить "финализатор" после обнаружения конца потока?

В последнем случае просто заверните вызов myLongRunningTask в другой метод:

void surrogateThreadRoutine() {
    // try{ ..

    mytask();

    // finally { ..
    ..all 'finalization'.. or i.e. raising some Event that you'll handle elsewhere
}

и использовать его как подпрограмму потока. Таким образом, вы узнаете, что финализация будет происходить в потоке и сразу после окончания фактического задания.

Однако, конечно, если вы работаете с некоторыми пользовательскими интерфейсами или другими планировщиками, теперь "финализация" будет работать на вашем потоке, а не на "нормальных потоках" вашего интерфейса или комм., Вам нужно будет убедиться, что все ресурсы являются внешними по отношению к вашей потоковой задаче, правильно защищены или синхронизированы, иначе вы, вероятно, столкнетесь с другими потоками приложений.

Например, в WinForms, прежде чем вы касаетесь каких-либо элементов пользовательского интерфейса из финализатора, вам понадобится Control.InvokeRequired(surely = true) и Control.BeginInvoke/Invoke, чтобы вернуть контекст обратно в поток пользовательского интерфейса.

Например, в WPF, прежде чем касаться каких-либо элементов пользовательского интерфейса от финализатора, вам понадобится Dispatcher.BeginInvoke..

Или, если столкновение может произойти с любыми потоками, которые вы контролируете, достаточно простого lock(). и др.

Ответ 3

Вы можете использовать комбинацию пользовательского события и использование BeginInvoke:

public event EventHandler MyLongRunningTaskEvent;

private void StartMyLongRunningTask() {
    MyLongRunningTaskEvent += myLongRunningTaskIsDone;
    Thread _thread = new Thread(myLongRunningTask) { IsBackground = true };
    _thread.Start();
    label.Text = "Running...";
}

private void myLongRunningTaskIsDone(object sender, EventArgs arg)
{
    label.Text = "Done!";
}

private void myLongRunningTask()
{
    try 
    { 
        // Do my long task...
    } 
    finally
    {
        this.BeginInvoke(Foo, this, EventArgs.Empty);
    }
}

Я проверил, он работает в .NET 3.5

Ответ 4

Попробуйте использовать ManualRestEvent для сигнала завершения потока.

Ответ 5

Вы можете использовать шаблон наблюдателя, посмотрите здесь:

http://www.dofactory.com/Patterns/PatternObserver.aspx

Шаблон наблюдателя позволит вам уведомлять другие объекты, которые ранее были определены как наблюдатель.

Ответ 6

Возможно использование условных переменных и мьютекса или некоторые функции, такие как wait(), signal(), возможно, timed wait(), чтобы не блокировать основной поток бесконечно.

В С# это будет:

   void Notify()
{
    lock (syncPrimitive)
    {
        Monitor.Pulse(syncPrimitive);
    }
}

void RunLoop()
{

    for (;;)
    {
        // do work here...

        lock (syncPrimitive)
        {
            Monitor.Wait(syncPrimitive);
        }
    }
}

подробнее об этом здесь: Переменные состояния С#/. NET

Это концепция объекта Monitor в С#, у вас также есть версия, которая позволяет установить тайм-аут

public static bool Wait(
   object obj,
   TimeSpan timeout
)

подробнее об этом здесь: https://msdn.microsoft.com/en-us/library/system.threading.monitor_methods(v=vs.110).aspx

Ответ 7

  1. Очень простой поток выполнения с обратным вызовом завершения
  2. Это не обязательно должно работать в монофоническом режиме и просто используется для удобства
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;

public class ThreadTest : MonoBehaviour
{
    private List<int> numbers = null;

    private void Start()
    {
        Debug.Log("1. Call thread task");

        StartMyLongRunningTask();

        Debug.Log("2. Do something else");
    }

    private void StartMyLongRunningTask()
    {
        numbers = new List<int>();

        ThreadStart starter = myLongRunningTask;

        starter += () =>
        {
            myLongRunningTaskDone();
        };

        Thread _thread = new Thread(starter) { IsBackground = true };
        _thread.Start();
    }

    private void myLongRunningTaskDone()
    {
        Debug.Log("3. Task callback result");

        foreach (int num in numbers)
            Debug.Log(num);
    }


    private void myLongRunningTask()
    {
        for (int i = 0; i < 10; i++)
        {
            numbers.Add(i);

            Thread.Sleep(1000);
        }
    }
}