Подтвердить что ты не робот

Обнаружение в потоке пользовательского интерфейса в 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");
        }
    }
}
4b9b3361

Ответ 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

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