web-dev-qa-db-fra.com

Détecter si sur un thread d'interface utilisateur dans WPF et Winforms

J'ai écrit une méthode d'assertion Ensure.CurrentlyOnUiThread () , ci-dessous, vérifiant que le thread actuel est un thread d'interface utilisateur. 

  • Est-ce que cela va être fiable pour détecter le fil de l'interface utilisateur Winforms?
  • Notre application est mixte WPF et Winforms, comment détecter au mieux un thread valide d'interface utilisateur WPF?
  • Y a-t-il une meilleure manière de faire cela? Peut-être des contrats de code?

Ensure.cs

using System.Diagnostics;
using System.Windows.Forms;

public static class Ensure
{
    [Conditional("DEBUG")]
    public static void CurrentlyOnUiThread()
    {
        if (!Application.MessageLoop)
        {
            throw new ThreadStateException("Assertion failed: not on the UI thread");
        }
    }
}
39
chillitom

Ne pas utiliser

if(Dispatcher.CurrentDispatcher.Thread == Thread.CurrentThread)
{
   // Do something
}

Dispatcher.CurrentDispatcher permettra, si le thread actuel ne dispose pas d'un répartiteur, de créer et de renvoyer une nouvelle Dispatcher associée au thread actuel.

Au lieu de faire comme ça

Dispatcher dispatcher = Dispatcher.FromThread(Thread.CurrentThread);
if (dispatcher != null)
{
   // We know the thread have a dispatcher that we can use.
}

Pour vous assurer que vous avez le bon répartiteur ou que vous avez le bon fil, vous avez les options suivantes

Dispatcher _myDispatcher;

public void UnknownThreadCalling()
{
    if (_myDispatcher.CheckAccess())
    {
        // Calling thread is associated with the Dispatcher
    }

    try
    {
        _myDispatcher.VerifyAccess();

        // Calling thread is associated with the Dispatcher
    }
    catch (InvalidOperationException)
    {
        // Thread can't use dispatcher
    }
}

CheckAccess() et VerifyAccess() n'apparaissent pas dans intellisense.

En outre, si vous devez recourir à ce genre de choses, cela est probablement dû à une mauvaise conception. Vous devriez savoir quels threads exécutent quel code dans votre programme.

46
CodeMonkey

Dans WinForm, vous utiliseriez normalement

if(control.InvokeRequired) 
{
 // Do non UI thread stuff
}

pour WPF

if (!control.Dispatcher.CheckAccess())
{
  // Do non UI Thread stuff
}

J'écrirais probablement une petite méthode qui utilise une contrainte générique pour déterminer laquelle vous devriez appeler. par exemple.

public static bool CurrentlyOnUiThread<T>(T control)
{ 
   if(T is System.Windows.Forms.Control)
   {
      System.Windows.Forms.Control c = control as System.Windows.Forms.Control;
      return !c.InvokeRequired;
   }
   else if(T is System.Windows.Controls.Control)
   {
      System.Windows.Controls.Control c = control as System.Windows.Control.Control;
      return c.Dispatcher.CheckAccess()
   }
}
19
Ian

Pour WPF:

// You are on WPF UI thread!
if (Thread.CurrentThread == System.Windows.Threading.Dispatcher.CurrentDispatcher.Thread)

Pour WinForms:

// You are NOT on WinForms UI thread for this control!
if (someControlOrWindow.InvokeRequired)
14
Matěj Zábský

Pour WPF, j'utilise les éléments suivants:

public static void InvokeIfNecessary (Action action)
{
    if (Thread.CurrentThread == Application.Current.Dispatcher.Thread)
        action ();
    else {
        Application.Current.Dispatcher.Invoke(action);
    }
}

La clé est au lieu de vérifier Dispatcher.CurrentDispatcher (qui vous donnera le répartiteur pour le thread actuel), vous devez vérifier si le thread actuel correspond au répartiteur de l'application ou à un autre contrôle.

9
Curtis

Peut-être que Control.InvokeRequired (WinForms) et Dispatcher.CheckAccess (WPF) vous conviennent?

5
Alex F

Vous insérez la connaissance de votre interface utilisateur dans votre logique. Ce n'est pas un bon design.

Votre couche d'interface utilisateur doit gérer les threads, car il ne faut pas abuser du thread d'interface utilisateur.

Cela vous permet également d'utiliser IsInvokeRequired dans les winforms et Dispatcher.Invoke dans WPF ... et vous permet d'utiliser votre code dans des requêtes asp.net synchrones et asynchrones ...

J'ai constaté en pratique qu'essayer de gérer le threading à un niveau inférieur de la logique de votre application ajoute souvent une complexité inutile. En fait, pratiquement tout le cadre est écrit avec ce point concédé - presque rien dans le cadre n'est thread-safe. Il appartient aux appelants (à un niveau supérieur) d’assurer la sécurité des threads.

2
Will

Pour WPF:

J'ai besoin de savoir si Dispatcher sur mon thread est réellement démarré ou non. Parce que si vous créez une classe WPF sur le thread, la réponse acceptée indiquera que le répartiteur est présent, même si vous ne faites jamais la fonction Dispatcher.Run(). J'ai fini par réfléchir:

public static class WpfDispatcherUtils
{
    private static readonly Type dispatcherType = typeof(Dispatcher);
    private static readonly FieldInfo frameDepthField = dispatcherType.GetField("_frameDepth", BindingFlags.Instance | BindingFlags.NonPublic);

    public static bool IsInsideDispatcher()
    {
        // get dispatcher for current thread
        Dispatcher currentThreadDispatcher = Dispatcher.FromThread(Thread.CurrentThread);

        if (currentThreadDispatcher == null)
        {
            // no dispatcher for current thread, we're definitely outside
            return false;
        }

        // get current dispatcher frame depth
        int currentFrameDepth = (int) frameDepthField.GetValue(currentThreadDispatcher);

        return currentFrameDepth != 0;
    }
}
1
Gman

Voici un extrait de code que j'utilise dans WPF pour intercepter des tentatives de modification des propriétés de l'interface utilisateur (qui implémentent INotifyPropertyChanged) à partir d'un thread autre que l'interface utilisateur:

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String info)
    {
        // Uncomment this to catch attempts to modify UI properties from a non-UI thread
        //bool oopsie = false;
        //if (Thread.CurrentThread != Application.Current.Dispatcher.Thread)
        //{
        //    oopsie = true; // place to set a breakpt
        //}

        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
0
Chris Bennet

Utiliser MVVM est en fait assez facile. Ce que je fais est de mettre quelque chose comme ce qui suit dans, disons, ViewModelBase ...

protected readonly SynchronizationContext SyncContext = SynchronizationContext.Current;

ou...

protected readonly TaskScheduler Scheduler = TaskScheduler.Current; 

Ensuite, lorsqu'un modèle de vue particulier doit toucher à quelque chose "d'observable", vous pouvez vérifier le contexte et réagir en conséquence ...

public void RefreshData(object state = null /* for direct calls */)
{
    if (SyncContext != SynchronizationContext.Current)
    {
        SyncContext.Post(RefreshData, null); // SendOrPostCallback
        return;
    }
    // ...
}

ou faire autre chose en arrière-plan avant de revenir au contexte ...

public void RefreshData()
{
    Task<MyData>.Factory.StartNew(() => GetData())
        .ContinueWith(t => {/* Do something with t.Result */}, Scheduler);
}

Normalement, si vous suivez MVVM (ou toute autre architecture) de manière ordonnée, il est facile de savoir où se trouvera la responsabilité de la synchronisation de l'interface utilisateur. Mais vous pouvez le faire n'importe où pour revenir au contexte dans lequel vos objets sont créés. Je suis sûr qu'il serait facile de créer un "Guard" pour gérer cela de manière propre et cohérente dans un système vaste et complexe. 

Je pense qu'il est logique de dire que votre seule responsabilité est de revenir à votre propre contexte d'origine. Il en va de la responsabilité du client de faire de même.

0
Ben Stabile