Определение наличия потока в пользовательском интерфейсе в WPF и Winforms - PullRequest
42 голосов
/ 28 февраля 2011

Я написал метод подтверждения Ensure.CurrentlyOnUiThread () ниже, который проверяет, что текущий поток является потоком пользовательского интерфейса.

  • Будет ли это надежным при обнаружении потока пользовательского интерфейса Winforms?
  • Наше приложение состоит из WPF и Winforms, как лучше всего определить действительный поток пользовательского интерфейса WPF?1009 * Есть ли лучший способ сделать это?Возможно кодовые контракты?

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

Ответы [ 10 ]

50 голосов
/ 11 января 2013

Не использовать

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.

Кроме того, если вам приходится прибегать к подобным вещам, это вероятно из-за плохого дизайна. Вы должны знать, какие потоки выполняют какой код в вашей программе.

20 голосов
/ 28 февраля 2011

В WinForms вы обычно используете

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

для WPF

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

Я бы, вероятно, написал небольшой метод, который использует общее ограничение, чтобы определить, какой из них вы должны вызывать. например,

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()
   }
}
14 голосов
/ 28 февраля 2011

Для 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)
10 голосов
/ 08 февраля 2013

Для WPF я использую следующее:

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

Ключ вместо проверки Dispatcher.CurrentDispatcher (который даст вам диспетчер для текущего потока), вам нужно проверить, соответствует ли текущий поток диспетчеру приложения или другого элемента управления.

6 голосов
/ 28 февраля 2011

Может быть, Control.InvokeRequired (WinForms) и Dispatcher.CheckAccess (WPF) вам подходят?

2 голосов
/ 28 февраля 2011

Вы вкладываете знания своего пользовательского интерфейса в свою логику. Это не хороший дизайн.

Ваш уровень пользовательского интерфейса должен обрабатывать потоки, поскольку обеспечение того, что поток пользовательского интерфейса не используется, входит в компетенцию пользовательского интерфейса.

Это также позволяет вам использовать IsInvokeRequired в winforms и Dispatcher.Invoke в WPF ... и позволяет использовать ваш код в синхронных и асинхронных запросах asp.net. ...

На практике я обнаружил, что попытка обработки потоков на более низком уровне в логике вашего приложения часто добавляет много ненужной сложности. Фактически, практически весь фреймворк написан с учетом этой точки зрения - почти ничего в фреймворке не является поточно-ориентированным. Это до звонящих (на более высоком уровне), чтобы обеспечить безопасность потока.

1 голос
/ 18 января 2014

Для 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;
    }
}
0 голосов
/ 04 ноября 2013

Вот фрагмент кода, который я использую в WPF для отлова попыток изменить свойства пользовательского интерфейса (которые реализуют INotifyPropertyChanged) из потока, не являющегося пользовательским интерфейсом:

    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 голосов
/ 27 сентября 2013

Использование 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 (или любой другой архитектуре) упорядоченным образом, легко определить, где будет лежать ответственность за синхронизацию пользовательского интерфейса. Но вы можете сделать это где угодно, чтобы вернуться в контекст, в котором создаются ваши объекты. Я уверен, что было бы легко создать «Guard», который бы справлялся с этим чисто и последовательно в большой и сложной системе.

Я думаю, что имеет смысл сказать, что ваша единственная обязанность - вернуться в свой первоначальный контекст. Это обязанность клиента сделать то же самое.

0 голосов
/ 04 ноября 2011
Thread.CurrentThread.ManagedThreadId == Dispatcher.Thread.ManagedThreadId

Это лучший способ проверить это

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...