50

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

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

确保.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");
        }
    }
}
4

12 回答 12

64

不要使用

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()不要出现在智能感知中。

此外,如果您不得不求助于这些类型的东西,这可能是由于糟糕的设计。您应该知道哪些线程在您的程序中运行哪些代码。

于 2013-01-11T14:41:52.720 回答
25

在 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()
   }
}
于 2011-02-28T15:04:26.433 回答
21

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

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

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

于 2013-02-08T10:40:56.660 回答
17

对于 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)
于 2011-02-28T15:02:50.523 回答
6

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

于 2011-02-28T15:05:25.280 回答
2

你正在将你的 UI 知识推到你的逻辑中。这不是一个好的设计。

您的 UI 层应该处理线程,因为确保 UI 线程不被滥用是在 UI 的范围内。

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

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

于 2011-02-28T15:06:37.823 回答
1

这是我在 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));
        }
    }
于 2013-11-04T15:13:14.730 回答
1

对于 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;
    }
}
于 2014-01-18T11:39:30.693 回答
1

您可以像这样比较线程 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
        }
于 2020-06-08T13:14:38.883 回答
0

使用 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 同步的责任在哪里。但是您基本上可以在任何地方执行此操作以返回创建对象的上下文。我敢肯定,在一个庞大而复杂的系统中,创建一个“守卫”来干净、一致地处理这个问题是很容易的。

我认为说你唯一的责任是回到你自己的原始环境是有道理的。客户也有责任这样做。

于 2013-09-27T16:17:43.103 回答
0

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

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

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

笔记; 和继承自Func所以我们可以直接转换它。ActionDelegate

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

        /// <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);
于 2020-04-03T11:53:38.973 回答
-4
Thread.CurrentThread.ManagedThreadId == Dispatcher.Thread.ManagedThreadId

是检查这个的更好方法

于 2011-11-04T13:46:31.397 回答