Wykrywanie, czy w wątku UI w WPF i Winforms

Napisałem metodę twierdzenia Ensure.CurrentlyOnUiThread () , poniżej sprawdza, czy bieżący wątek jest wątkiem interfejsu użytkownika.

  • czy to będzie niezawodne w wykrywaniu wątku interfejsu Winforms?
  • nasza aplikacja jest mieszana WPF i Winforms, jak najlepiej wykryć poprawny wątek interfejsu WPF?
  • Czy jest na to lepszy sposób? Może kod umowy?

Upewnij się.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");
        }
    }
}
Author: chillitom, 1970-01-01

10 answers

Nie używaj

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

Dispatcher.CurrentDispatcher Jeśli bieżący wątek nie ma dyspozytora, utworzy i zwróci nową Dispatcher powiązaną z bieżącym wątkiem.

Zamiast robić tak

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

Aby mieć pewność, że posiadasz poprawny dyspozytor lub jesteś w odpowiednim wątku, masz następujące opcje

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() i VerifyAccess() nie pojawiają się w intellisense.

Również, jeśli musisz uciekać się do tego rodzaju rzeczy, prawdopodobnie z powodu złego projektu. Powinieneś wiedzieć, który wątki uruchomić jaki kod w programie.

 43
Author: CodeMonkey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-11 14:41:52

W WinForms normalnie używasz

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

Dla WPF

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

Prawdopodobnie napisałbym małą metodę, która wykorzystuje ogólne Ograniczenie, aby określić, które z nich należy wywołać. np.

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()
   }
}
 17
Author: Ian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-14 07:59:50

Dla WPF:

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

Dla WinForms:

// You are NOT on WinForms UI thread for this control!
if (someControlOrWindow.InvokeRequired)
 14
Author: Matěj Zábský,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-14 20:20:05

Do WPF używam:

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

Klucz jest zamiast sprawdzania dyspozytora.CurrentDispatcher (który da ci dyspozytora dla bieżącego wątku), musisz sprawdzić, czy bieżący wątek pasuje do dyspozytora aplikacji lub innej kontroli.

 7
Author: Curtis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-27 19:30:18

Może Control.InvokeRequired (WinForms) i Dispatcher.CheckAccess (WPF) są dla Ciebie w porządku?

 5
Author: Alex F,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-19 13:42:47

Wpychasz wiedzę o swoim interfejsie użytkownika do swojej logiki. To nie jest dobry projekt.

Twoja warstwa interfejsu powinna obsługiwać wątki, ponieważ zapewnienie, że wątek interfejsu nie jest nadużywany, jest w zasięgu interfejsu.

Pozwala to również na użycie IsInvokeRequired w winforms i Dispatcher.Wywołaj w WPF... i pozwala na wykorzystanie kodu w ramach synchronicznych i asynchronicznych asp.net prośby również...

Odkryłem w praktyce, że próbując poradzić sobie tworzenie wątków na niższym poziomie logiki aplikacji często zwiększa niepotrzebną złożoność. W rzeczywistości praktycznie cały framework jest napisany z tym punktem-prawie nic w frameworku nie jest bezpieczne. Jego do wywołujących (na wyższym poziomie) w celu zapewnienia bezpieczeństwa wątku.

 2
Author: Will,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-28 15:06:37

Dla WPF:

Muszę wiedzieć, czy Dyspozytor na moim wątku jest rzeczywiście uruchomiony, czy nie. Ponieważ jeśli utworzysz jakąkolwiek klasę WPF w wątku, zaakceptowana odpowiedź stwierdzi, że dispatcher tam jest, nawet jeśli nigdy nie wykonasz Dispatcher.Run(). Skończyło się na refleksji:

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
Author: Gman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-18 11:39:30

Korzystanie z MVVM jest w rzeczywistości dość łatwe. To, co robię, to umieszczam coś w rodzaju następującego, powiedzmy, ViewModelBase...

protected readonly SynchronizationContext SyncContext = SynchronizationContext.Current;

Lub...

protected readonly TaskScheduler Scheduler = TaskScheduler.Current; 

Wtedy, gdy konkretny model widoku musi dotknąć czegokolwiek "obserwowalnego", możesz sprawdzić kontekst i odpowiednio zareagować...

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

Lub zrobić coś innego w tle przed powrotem do kontekstu ...

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

Zwykle, jeśli podążasz za MVVM (lub inną architekturą) w uporządkowany sposób, łatwo jest powiedzieć, gdzie odpowiedzialność za synchronizację UI będzie umiejscowiona. Ale możesz to zrobić w dowolnym miejscu, aby powrócić do kontekstu, w którym tworzone są obiekty. Jestem pewien, że łatwo byłoby stworzyć "osłonę", aby poradzić sobie z tym czysto i konsekwentnie w dużym i złożonym systemie.

Myślę, że sensowne jest stwierdzenie, że twoim jedynym obowiązkiem jest powrót do pierwotnego kontekstu. To samo jest obowiązkiem klienta.
 0
Author: Ben Stabile,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-27 16:17:43

Oto fragment kodu, którego używam w WPF do przechwytywania prób modyfikacji właściwości UI (które implementują INotifyPropertyChanged) z nie-UI wątku:

    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
Author: Chris Bennet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-04 15:13:14
Thread.CurrentThread.ManagedThreadId == Dispatcher.Thread.ManagedThreadId

Is a better way to check this

 -3
Author: kevinhaesendonckx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-08 20:12:41