Detecting whether on UI thread in WPF and Winforms

纵饮孤独 提交于 2019-11-26 13:48:13

问题


I've written an assertion method Ensure.CurrentlyOnUiThread(), below, that checks that the current thread is a UI thread.

  • Is this going to be reliable in detecting the Winforms UI thread?
  • Our app is mixed WPF and Winforms, how best to detect a valid WPF UI thread?
  • Is there a better way to do this? Perhaps code contracts?

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:


Don't use

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

Dispatcher.CurrentDispatcher will, if the current thread do not have a dispatcher, create and return a new Dispatcher associated with the current thread.

Instead do like this

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

To be sure you have the correct dispatcher or are on the correct thread you have the following options

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() and VerifyAccess() do not show up in intellisense.

Also, if you have to resort to these kinds of things its likely due to bad design. You should know which threads run what code in your program.




回答2:


Within WinForms you would normally use

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

for WPF

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

I would probably write a little method that uses a Generic constraint to determine which of these you should be calling. e.g.

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:


For WPF:

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

For WinForms:

// You are NOT on WinForms UI thread for this control!
if (someControlOrWindow.InvokeRequired)



回答4:


For WPF, I use the following:

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

The key is instead of checking Dispatcher.CurrentDispatcher (which will give you the dispatcher for the current thread), you need to check if the current thread matches the dispatcher of the application or another control.




回答5:


Maybe Control.InvokeRequired (WinForms) and Dispatcher.CheckAccess (WPF) are OK for you?




回答6:


You're pushing knowledge of your UI down into your logic. This is not a good design.

Your UI layer should be handling threading, as ensuring the UI thread isn't abused is within the purview of the UI.

This also allows you to use IsInvokeRequired in winforms and Dispatcher.Invoke in WPF... and allows you to use your code within synchronous and asynchronous asp.net requests as well...

I've found in practice that trying to handle threading at a lower level within your application logic often adds lots of unneeded complexity. In fact, practically the entire framework is written with this point conceded--almost nothing in the framework is thread safe. Its up to callers (at a higher level) to ensure thread safety.




回答7:


For WPF:

I've needed to know is Dispatcher on my thread is actually started, or not. Because if you create any WPF class on the thread, the accepted answer will state that the dispatcher is there, even if you never do the Dispatcher.Run(). I've ended up with some reflection:

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



回答8:


Using MVVM it is actually fairly easy. What I do is put something like the following in, say, ViewModelBase...

protected readonly SynchronizationContext SyncContext = SynchronizationContext.Current;

or...

protected readonly TaskScheduler Scheduler = TaskScheduler.Current; 

Then when a particular ViewModel needs to touch anything "observable", you can check the context and react accordingly...

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

or do something else in the background before returning to context ...

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

Normally, if you follow MVVM (or any other architecture) in an orderly fashion, it is easy to tell where the responsibility for UI synchronization will be situated. But you can basically do this anywhere to return to the context where your objects are created. I'm sure it would be easy to create a "Guard" to handle this cleanly and consistently in a large and complex system.

I think it makes sense to say that your only responsibility is to get back to your own original context. It is a client's responsibility to do the same.




回答9:


Here is a snippet of code I use in WPF to catch attempts to modify UI Properties (that implement INotifyPropertyChanged) from a non-UI thread:

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



回答10:


Thread.CurrentThread.ManagedThreadId == Dispatcher.Thread.ManagedThreadId

Is a better way to check this



来源:https://stackoverflow.com/questions/5143599/detecting-whether-on-ui-thread-in-wpf-and-winforms

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!