Je travaille sur une application héritée construite sur NET 3.5. C'est une contrainte que je ne peux pas changer. Je dois exécuter un deuxième thread pour exécuter une tâche longue sans verrouiller l'interface utilisateur. Lorsque le fil est terminé, je dois exécuter un rappel.
En ce moment, j'ai essayé ce pseudo-code:
Thread _thread = new Thread(myLongRunningTask) { IsBackground = True };
_tread.Start();
// wait until it's done
_thread.Join();
// execute finalizer
La deuxième option, qui ne verrouille pas l'interface utilisateur, est la suivante:
Thread _thread = new Thread(myLongRunningTask) { IsBackground = True };
_tread.Start();
// wait until it's done
while(_thread.IsAlive)
{
Application.DoEvents();
Thread.Sleep(100);
}
// execute finalizer
Bien sûr, la deuxième solution n’est pas bonne car elle surchargerait l’UI. Quelle est la bonne façon d’exécuter un rappel lorsque le _thread est terminé? Aussi, comment savoir si le fil de discussion a été annulé ou abandonné?
* Remarque: * Je ne peux pas utiliser BackgroundWorker ni la bibliothèque Async, je dois travailler avec la classe de thread native.
L'approche la plus simple consiste à ajouter un rappel, essentiellement. Vous pouvez même le faire en utilisant simplement le mode de fonctionnement des délégués de multidiffusion:
ThreadStart starter = myLongRunningTask;
starter += () => {
// Do what you want in the callback
};
Thread thread = new Thread(starter) { IsBackground = true };
thread.Start();
C'est très vanille, et le rappel ne sera pas déclenché si le thread est abandonné ou lève une exception. Vous pouvez l'envelopper dans une classe avec plusieurs rappels ou un rappel qui spécifie le statut (abandonné, levé une exception, etc.) et le gère en encapsulant le délégué d'origine, en l'appelant dans une méthode avec un bloc try
/catch
et en l'exécutant. le rappel de manière appropriée.
À moins que vous n'effectuiez une action spéciale, le rappel sera exécuté in le thread d'arrière-plan. Vous devrez donc utiliser Control.BeginInvoke
(ou autre) pour réintégrer le thread d'interface utilisateur.
Je comprends tout à fait vos exigences, mais vous avez oublié une chose cruciale: avez-vous besoin {vraiment} d'attendre la fin de ce fil de manière synchrone? Ou peut-être avez-vous simplement besoin d'exécuter le "finaliseur" après la détection de la fin du thread?
Dans ce dernier cas, enveloppez simplement l'appel à myLongRunningTask
dans une autre méthode:
void surrogateThreadRoutine() {
// try{ ..
mytask();
// finally { ..
..all 'finalization'.. or i.e. raising some Event that you'll handle elsewhere
}
et l'utiliser comme routine du fil. De cette manière, vous saurez que la finalisation aura lieu au niveau du thread et juste après la fin du travail.
Cependant, bien sûr, si vous utilisez une interface utilisateur ou d'autres planificateurs, la "finalisation" sera désormais exécutée sur votre fil, et non sur les "fils normaux" de votre interface utilisateur ou de votre cadre de communication. Vous devrez vous assurer que toutes les ressources externes à votre tâche de fil sont correctement protégées ou synchronisées, sinon vous risquez de vous heurter à d'autres threads de l'application.
Par exemple, dans WinForms, avant de toucher des éléments d'interface utilisateur depuis le finaliseur, vous aurez besoin de Control.InvokeRequired (surely = true) et de Control.BeginInvoke/Invoke pour renvoyer le contexte vers le thread d'interface utilisateur.
Par exemple, dans WPF, avant de toucher des éléments d'interface utilisateur du finaliseur, vous aurez besoin de Dispatcher.BeginInvoke ..
Ou, si le conflit pouvait se produire avec l'un des threads que vous contrôlez, un simple lock()
propre correct pourrait suffire. etc.
Peut-être en utilisant des variables conditionnelles et des mutex, ou des fonctions comme wait (), signal (), peut-être timed wait () pour ne pas bloquer le thread principal à l'infini.
En C # ce sera:
void Notify()
{
lock (syncPrimitive)
{
Monitor.Pulse(syncPrimitive);
}
}
void RunLoop()
{
for (;;)
{
// do work here...
lock (syncPrimitive)
{
Monitor.Wait(syncPrimitive);
}
}
}
plus à ce sujet ici: Variables de condition C # /. NET
C'est le concept d'objet Monitor en C #, vous avez également une version qui permet de définir le délai
public static bool Wait(
object obj,
TimeSpan timeout
)
plus à ce sujet ici: https://msdn.Microsoft.com/en-us/library/system.threading.monitor_methods(v=vs.110).aspx
Vous pouvez utiliser une combinaison d'événement personnalisé et l'utilisation de 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);
}
}
J'ai vérifié, ça marche sous .NET 3.5
Essayez d'utiliser ManualRestEvent pour signaler que le thread est complet.
Vous pouvez utiliser le modèle Observer, jetez un oeil ici:
http://www.dofactory.com/Patterns/PatternObserver.aspx
Le modèle d'observateur vous permettra d'informer d'autres objets précédemment définis en tant qu'observateur.