检测是否在 WPF 和 Winforms 中的 UI 线程上

发布于 2024-10-19 17:23:36 字数 573 浏览 8 评论 0原文

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

  • 这对于检测 Winforms UI 线程可靠吗?
  • 我们的应用程序混合了 WPF 和 Winforms,如何最好地检测有效的 WPF UI 线程?
  • 有更好的方法吗?也许代码合同?

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

剪不断理还乱 2024-10-26 17:23:37

不要使用

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() 不会显示在智能感知中。

另外,如果你不得不诉诸这些东西,很可能是由于糟糕的设计。您应该知道哪些线程运行程序中的哪些代码。

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.

水水月牙 2024-10-26 17:23:37

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

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

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

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.

梦年海沫深 2024-10-26 17:23:37

在 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()
   }
}

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()
   }
}
抽个烟儿 2024-10-26 17:23:37

对于 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)

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)
忆依然 2024-10-26 17:23:37

也许 Control.InvokeRequired (WinForms) 和 Dispatcher.CheckAccess (WPF) 适合您?

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

幽梦紫曦~ 2024-10-26 17:23:37

您正在将 UI 知识融入您的逻辑中。这不是一个好的设计。

您的 UI 层应该处理线程,因为确保 UI 线程不被滥用属于 UI 的权限范围。

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

我在实践中发现,尝试在应用程序逻辑中的较低级别处理线程通常会增加许多不必要的复杂性。事实上,实际上整个框架都是在承认这一点的情况下编写的——框架中几乎没有什么是线程安全的。确保线程安全由调用者(更高级别)决定。

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.

蝶舞 2024-10-26 17:23:37

下面是我在 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));
        }
    }

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));
        }
    }
等风来 2024-10-26 17:23:37

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

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;
    }
}
氛圍 2024-10-26 17:23:37

您可以像这样比较线程 ID:

       var managedThreadId = System.Windows.Threading.Dispatcher.FromThread(System.Threading.Thread.CurrentThread)?.Thread.ManagedThreadId;
        var dispatcherManagedThreadId = System.Windows.Application.Current.Dispatcher.Thread.ManagedThreadId;
        if (managedThreadId == dispatcherManagedThreadId)
        {
             //works in ui dispatcher thread
        }

You can compare thread ids like this :

       var managedThreadId = System.Windows.Threading.Dispatcher.FromThread(System.Threading.Thread.CurrentThread)?.Thread.ManagedThreadId;
        var dispatcherManagedThreadId = System.Windows.Application.Current.Dispatcher.Thread.ManagedThreadId;
        if (managedThreadId == dispatcherManagedThreadId)
        {
             //works in ui dispatcher thread
        }
归属感 2024-10-26 17:23:37

使用 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(或任何其他架构),则很容易知道 UI 同步的责任将位于何处。但基本上您可以在任何地方执行此操作以返回到创建对象的上下文。我相信在一个大型复杂的系统中创建一个“警卫”来干净一致地处理这个问题是很容易的。

我认为说你唯一的责任是回到你自己最初的背景是有道理的。客户有责任这样做。

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.

烏雲後面有陽光 2024-10-26 17:23:37

对于 WPF:

这是基于最佳答案的片段,使用委托意味着它非常通用。

        /// <summary>
        /// Invokes the Delegate directly on the main UI thread, based on the calling threads' <see cref="Dispatcher"/>.
        /// NOTE this is a blocking call.
        /// </summary>
        /// <param name="method">Method to invoke on the Main ui thread</param>
        /// <param name="args">Argumens to pass to the method</param>
        /// <returns>The return object of the called object, which can be null.</returns>
        private object InvokeForUiIfNeeded(Delegate method, params object[] args)
        {
            if (method == null) throw new ArgumentNullException(nameof(method));

            var dispatcher = Application.Current.Dispatcher;

            if (dispatcher.Thread != Thread.CurrentThread)
            {
                // We're on some other thread, Invoke it directly on the main ui thread.
                return dispatcher.Invoke(method, args);
            }
            else
            {
                // We're on the dispatchers' thread, which (in wpf) is the main UI thread.
                // We can safely update ui here, and not going through the dispatcher which safes some (minor) overhead.
                return method.DynamicInvoke(args);
            }

        }

        /// <inheritdoc cref="InvokeForUiIfNeeded(Delegate, object[])"/>
        public TReturn InvokeForUiIfNeeded<TReturn>(Delegate method, params object[] args)
            => (TReturn) InvokeForUiIfNeeded(method, args);

第二种方法允许类型更安全的返回类型。
我还添加了一些重载,这些重载会自动采用代码中的 FuncAction 参数,例如

        /// <inheritdoc cref="InvokeForUiIfNeeded(System.Delegate, object[])"/>
        private void InvokeForUiIfNeeded(Action action)
            => InvokeForUiIfNeeded((Delegate) action);

FuncAction 继承自 Delegate,因此我们可以直接转换它。

您还可以添加自己的通用重载来执行操作,我没有费心创建一堆重载,但您绝对可以,例如;

        /// <inheritdoc cref="InvokeForUiIfNeeded(System.Delegate, object[])"/>
        private void InvokeForUiIfNeeded<T1>(Action<T1> action, T1 p1)
            => InvokeForUiIfNeeded((Delegate)action, p1);

        /// <inheritdoc cref="InvokeForUiIfNeeded(System.Delegate, object[])"/>
        private TReturn InvokeForUiIfNeeded<T1, TReturn>(Func<T1, TReturn> action, T1 p1)
            => (TReturn)InvokeForUiIfNeeded((Delegate)action, p1);

FOR WPF:

Here's a snippet based on the top answer, using a delegate meaning it is very generic.

        /// <summary>
        /// Invokes the Delegate directly on the main UI thread, based on the calling threads' <see cref="Dispatcher"/>.
        /// NOTE this is a blocking call.
        /// </summary>
        /// <param name="method">Method to invoke on the Main ui thread</param>
        /// <param name="args">Argumens to pass to the method</param>
        /// <returns>The return object of the called object, which can be null.</returns>
        private object InvokeForUiIfNeeded(Delegate method, params object[] args)
        {
            if (method == null) throw new ArgumentNullException(nameof(method));

            var dispatcher = Application.Current.Dispatcher;

            if (dispatcher.Thread != Thread.CurrentThread)
            {
                // We're on some other thread, Invoke it directly on the main ui thread.
                return dispatcher.Invoke(method, args);
            }
            else
            {
                // We're on the dispatchers' thread, which (in wpf) is the main UI thread.
                // We can safely update ui here, and not going through the dispatcher which safes some (minor) overhead.
                return method.DynamicInvoke(args);
            }

        }

        /// <inheritdoc cref="InvokeForUiIfNeeded(Delegate, object[])"/>
        public TReturn InvokeForUiIfNeeded<TReturn>(Delegate method, params object[] args)
            => (TReturn) InvokeForUiIfNeeded(method, args);

The second method allows for a more type safe return type.
I've also added some overloads that automatically take the Func and Action parameters in my code, e.g:

        /// <inheritdoc cref="InvokeForUiIfNeeded(System.Delegate, object[])"/>
        private void InvokeForUiIfNeeded(Action action)
            => InvokeForUiIfNeeded((Delegate) action);

Note; the Func and Action inherit from Delegate so we can just cast it.

You could also add your own generic overloads that take actions, i did not bother creating a bunch of overloads but you definitely could e.g;

        /// <inheritdoc cref="InvokeForUiIfNeeded(System.Delegate, object[])"/>
        private void InvokeForUiIfNeeded<T1>(Action<T1> action, T1 p1)
            => InvokeForUiIfNeeded((Delegate)action, p1);

        /// <inheritdoc cref="InvokeForUiIfNeeded(System.Delegate, object[])"/>
        private TReturn InvokeForUiIfNeeded<T1, TReturn>(Func<T1, TReturn> action, T1 p1)
            => (TReturn)InvokeForUiIfNeeded((Delegate)action, p1);
绝情姑娘 2024-10-26 17:23:37
Thread.CurrentThread.ManagedThreadId == Dispatcher.Thread.ManagedThreadId

是检查这个的更好方法

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

Is a better way to check this

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文