Уведомлять, когда поток завершен, без блокировки вызывающей нити
Я работаю над устаревшим приложением, которое построено поверх 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
- Очень простой поток выполнения с обратным вызовом завершения
- Это не обязательно должно работать в монофоническом режиме и просто используется для удобства
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);
}
}
}