Обнаружение в потоке пользовательского интерфейса в WPF и Winforms
Я написал метод подтверждения Ensure.CurrentlyOnUiThread(), который показывает, что текущий поток является потоком пользовательского интерфейса.
- Будет ли это надежным в обнаружении потока пользовательских интерфейсов Winforms?
- Наше приложение является смешанным WPF и Winforms, как лучше всего определить действительный поток пользовательского интерфейса WPF?
- Есть ли лучший способ сделать это? Возможно, код заключен в контракты?
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");
}
}
}
Ответы
Ответ 1
Не используйте
if(Dispatcher.CurrentDispatcher.Thread == Thread.CurrentThread)
{
// Do something
}
Dispatcher.CurrentDispatcher
будет, если текущий поток не имеет диспетчера, создайте и верните новый Dispatcher
, связанный с текущим потоком.
Вместо этого сделайте это
Dispatcher dispatcher = Dispatcher.FromThread(Thread.CurrentThread);
if (dispatcher != null)
{
// We know the thread have a dispatcher that we can use.
}
Чтобы убедиться, что у вас есть правильный диспетчер или вы находитесь в правильном потоке, у вас есть следующие параметры.
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()
и VerifyAccess()
не отображаются в intellisense.
Кроме того, если вам приходится прибегать к подобным вещам, это, вероятно, связано с плохим дизайном. Вы должны знать, какие потоки запускают код в вашей программе.
Ответ 2
Внутри WinForms вы обычно используете
if(control.InvokeRequired)
{
// Do non UI thread stuff
}
для WPF
if (!control.Dispatcher.CheckAccess())
{
// Do non UI Thread stuff
}
Я бы, вероятно, написал небольшой метод, который использует ограничение Generic для определения того, из какого из них вы должны звонить. например.
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()
}
}
Ответ 3
Для WPF:
// You are on WPF UI thread!
if (Thread.CurrentThread == System.Windows.Threading.Dispatcher.CurrentDispatcher.Thread)
Для WinForms:
// You are NOT on WinForms UI thread for this control!
if (someControlOrWindow.InvokeRequired)
Ответ 4
Может быть, Control.InvokeRequired
(WinForms) и Dispatcher.CheckAccess
(WPF) подходят для вас?
Ответ 5
Для WPF я использую следующее:
public static void InvokeIfNecessary (Action action)
{
if (Thread.CurrentThread == Application.Current.Dispatcher.Thread)
action ();
else {
Instance.Invoke(action);
}
}
Ключ вместо проверки Dispatcher.CurrentDispatcher(который даст вам диспетчер для текущего потока), вам нужно проверить, соответствует ли текущий поток диспетчеру приложения или другому элементу управления.
Ответ 6
Вы подталкиваете знание своего интерфейса к своей логике. Это не очень хороший дизайн.
Уровень пользовательского интерфейса должен обрабатывать потоки, так как предотвращение злоупотребления потоком пользовательского интерфейса не входит в компетенцию пользовательского интерфейса.
Это также позволяет использовать IsInvokeRequired в winforms и Dispatcher.Invoke в WPF... и позволяет использовать ваш код в синхронных и асинхронных запросах asp.net, а также...
Я нашел на практике, что попытка обработки потоков на более низком уровне в вашей логике приложения часто добавляет много ненужной сложности. Фактически, практически вся структура написана с этой точки зрения - почти ничто в структуре не является потокобезопасным. Его доступ к вызывающим абонентам (на более высоком уровне) для обеспечения безопасности потоков.
Ответ 7
Использование MVVM на самом деле довольно просто. То, что я делаю, это что-то вроде следующего, например, в ViewModelBase...
protected readonly SynchronizationContext SyncContext = SynchronizationContext.Current;
или...
protected readonly TaskScheduler Scheduler = TaskScheduler.Current;
Затем, когда конкретный ViewModel должен касаться чего-либо "наблюдаемого", вы можете проверить контекст и соответствующим образом отреагировать...
public void RefreshData(object state = null /* for direct calls */)
{
if (SyncContext != SynchronizationContext.Current)
{
SyncContext.Post(RefreshData, null); // SendOrPostCallback
return;
}
// ...
}
или сделать что-то еще в фоновом режиме, прежде чем вернуться в контекст...
public void RefreshData()
{
Task<MyData>.Factory.StartNew(() => GetData())
.ContinueWith(t => {/* Do something with t.Result */}, Scheduler);
}
Обычно, если вы будете следовать MVVM (или любой другой архитектуре) упорядоченным образом, легко определить, где будет установлена ответственность за синхронизацию пользовательского интерфейса. Но вы можете в принципе сделать это в любом месте, чтобы вернуться в контекст, где созданы ваши объекты. Я уверен, что было бы легко создать "гвардию", чтобы обрабатывать это чисто и последовательно в большой и сложной системе.
Я думаю, что имеет смысл сказать, что ваша единственная ответственность - вернуться в свой собственный оригинальный контекст. Ответственность клиента заключается в том, чтобы сделать то же самое.
Ответ 8
Вот фрагмент кода, который я использую в WPF, чтобы поймать попытки изменить свойства интерфейса (которые реализуют INotifyPropertyChanged) из потока, отличного от UI:
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));
}
}
Ответ 9
Для WPF:
Мне нужно знать, что Dispatcher на моем потоке фактически запущен, или нет. Поскольку, если вы создаете какой-либо класс WPF в потоке, в принятом ответе указывается, что диспетчер существует, даже если вы никогда не делаете Dispatcher.Run()
. Я получил некоторое отражение:
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;
}
}
Ответ 10
Thread.CurrentThread.ManagedThreadId == Dispatcher.Thread.ManagedThreadId
Это лучший способ проверить этот