在WPF和Winforms中检测UI线程

时间:2021-07-23 20:58:28

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

我在下面编写了一个断言方法Ensure.CurrentlyOnUiThread(),它检查当前线程是否是UI线程。

  • Is this going to be reliable in detecting the Winforms UI thread?
  • 这在检测Winforms UI线程时是否可靠?
  • Our app is mixed WPF and Winforms, how best to detect a valid WPF UI thread?
  • 我们的app混合了WPF和Winforms,如何最好地检测有效的WPF UI线程?
  • Is there a better way to do this? Perhaps code contracts?
  • 有更好的方法吗?也许代码契约?

Ensure.cs

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 个解决方案

#1


44  

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.

调度员。如果当前线程没有dispatcher,那么将创建并返回一个与当前线程关联的新的dispatcher。

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.

CheckAccess()和VerifyAccess()不显示在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


18  

Within WinForms you would normally use

在WinForms中通常使用

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

for WPF

为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


15  

For WPF:

WPF:

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

For WinForms:

WinForms:

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

#4


5  

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

也许控制。InvokeRequired(WinForms)和调度员。CheckAccess (WPF)对您合适吗?

#5


5  

For WPF, I use the following:

对于WPF,我使用以下内容:

public static void InvokeIfNecessary (Action action)
{
    if (Thread.CurrentThread == Application.Current.Dispatcher.Thread)
        action ();
    else {
        Instance.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.

关键是不要检查调度程序。CurrentDispatcher(将为当前线程提供分派器)需要检查当前线程是否与应用程序的分派器或其他控件的分派器匹配。

#6


2  

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

你把UI的知识输入到你的逻辑中。这不是一个好的设计。

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

您的UI层应该处理线程,因为确保UI线程没有被滥用在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...

这还允许您在winforms和Dispatcher中使用IsInvokeRequired。在WPF调用…并且允许您在同步和异步asp.net请求中使用代码……

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


1  

For WPF:

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:

我需要知道线程上的Dispatcher是否已经启动。因为如果您在线程上创建任何WPF类,那么所接受的答案将表明dispatcher存在,即使您从未执行过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;
    }
}

#8


0  

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

使用MVVM实际上相当简单。我所做的就是在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...

然后,当一个特定的ViewModel需要访问任何“可观察”的内容时,您可以检查上下文并作出相应的反应……

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.

通常,如果您遵循MVVM(或任何其他架构)以一种有序的方式,那么很容易判断UI同步的责任将位于何处。但是你基本上可以在任何地方这样做,以返回到创建对象的上下文。我确信,在一个庞大而复杂的系统中,创建一个“警卫”来干净、始终如一地处理这个问题是很容易的。

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


0  

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:

下面是我在WPF中使用的一段代码,用于捕获从非UI线程修改UI属性(实现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));
        }
    }

#10


-3  

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

Is a better way to check this

是更好的检查方法吗

#1


44  

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.

调度员。如果当前线程没有dispatcher,那么将创建并返回一个与当前线程关联的新的dispatcher。

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.

CheckAccess()和VerifyAccess()不显示在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


18  

Within WinForms you would normally use

在WinForms中通常使用

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

for WPF

为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


15  

For WPF:

WPF:

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

For WinForms:

WinForms:

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

#4


5  

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

也许控制。InvokeRequired(WinForms)和调度员。CheckAccess (WPF)对您合适吗?

#5


5  

For WPF, I use the following:

对于WPF,我使用以下内容:

public static void InvokeIfNecessary (Action action)
{
    if (Thread.CurrentThread == Application.Current.Dispatcher.Thread)
        action ();
    else {
        Instance.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.

关键是不要检查调度程序。CurrentDispatcher(将为当前线程提供分派器)需要检查当前线程是否与应用程序的分派器或其他控件的分派器匹配。

#6


2  

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

你把UI的知识输入到你的逻辑中。这不是一个好的设计。

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

您的UI层应该处理线程,因为确保UI线程没有被滥用在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...

这还允许您在winforms和Dispatcher中使用IsInvokeRequired。在WPF调用…并且允许您在同步和异步asp.net请求中使用代码……

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


1  

For WPF:

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:

我需要知道线程上的Dispatcher是否已经启动。因为如果您在线程上创建任何WPF类,那么所接受的答案将表明dispatcher存在,即使您从未执行过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;
    }
}

#8


0  

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

使用MVVM实际上相当简单。我所做的就是在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...

然后,当一个特定的ViewModel需要访问任何“可观察”的内容时,您可以检查上下文并作出相应的反应……

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.

通常,如果您遵循MVVM(或任何其他架构)以一种有序的方式,那么很容易判断UI同步的责任将位于何处。但是你基本上可以在任何地方这样做,以返回到创建对象的上下文。我确信,在一个庞大而复杂的系统中,创建一个“警卫”来干净、始终如一地处理这个问题是很容易的。

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


0  

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:

下面是我在WPF中使用的一段代码,用于捕获从非UI线程修改UI属性(实现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));
        }
    }

#10


-3  

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

Is a better way to check this

是更好的检查方法吗