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");
}
}
}
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.
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()
}
}
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)
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.
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?
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.
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;
}
}
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.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));
}
}
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
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