了解Silverlight Dispatcher

25

我遇到了一个"Invalid Cross Thread Access"的问题,但是通过一些研究,我设法使用调度程序来解决它。

现在,在我的应用中,我有使用延迟加载的对象。我使用 WCF 进行异步调用,并像往常一样使用 Dispatcher 来更新我的对象 DataContext,但是对于这种情况它没有起作用。不过我在这里找到了解决方法。以下是我不理解的部分。

在我的 UserControl 中,我有一个调用对象上的方法的代码。对该方法的调用在 Dispatcher 中执行,如下所示。

Dispatcher.BeginInvoke( () => _CurrentPin.ToggleInfoPanel() );

正如我之前提到的,这还不足以满足Silverlight的需求,我必须在我的对象内再进行另一个 Dispatcher 调用。我的对象不是一个UIElement,它只是一个处理自己加载/保存的简单类。

因此,问题被修复通过调用:

Deployment.Current.Dispatcher.BeginInvoke( () => dataContext.Detail = detail );

在我的类中。

我为什么必须调用两次Dispatcher才能实现这个?高级别的调用不应该足够吗?Deployment.Current.Dispatcher和UIElement中的Dispatcher之间有什么区别吗?


1
我认为您提供的代码不够。 我猜测您正在执行某些操作触发了网络调用,该调用在后台线程中返回,因此会有两个调用? - Jason Jackson
2个回答

21
理想情况下,存储一个单一的Dispatcher实例,您可以在其他地方使用它而不必对线程进行检查。
调用任何单例的.Current实例可能会导致调用跨线程访问检查。通过先存储它,您可以避免这种情况,以实际获得共享实例。
我使用一个“SmartDispatcher”,当在非UI线程中调用时使用调度程序,并在其他情况下直接调用。它解决了这种问题。
文章链接:http://www.jeff.wilcox.name/2010/04/propertychangedbase-crossthread/ 代码:
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.

using System.ComponentModel;

namespace System.Windows.Threading
{
    /// <summary>
    /// A smart dispatcher system for routing actions to the user interface
    /// thread.
    /// </summary>
    public static class SmartDispatcher
    {
        /// <summary>
        /// A single Dispatcher instance to marshall actions to the user
        /// interface thread.
        /// </summary>
        private static Dispatcher _instance;

        /// <summary>
        /// Backing field for a value indicating whether this is a design-time
        /// environment.
        /// </summary>
        private static bool? _designer;

        /// <summary>
        /// Requires an instance and attempts to find a Dispatcher if one has
        /// not yet been set.
        /// </summary>
        private static void RequireInstance()
        {
            if (_designer == null)
            {
                _designer = DesignerProperties.IsInDesignTool;
            }

            // Design-time is more of a no-op, won't be able to resolve the
            // dispatcher if it isn't already set in these situations.
            if (_designer == true)
            {
                return;
            }

            // Attempt to use the RootVisual of the plugin to retrieve a
            // dispatcher instance. This call will only succeed if the current
            // thread is the UI thread.
            try
            {
                _instance = Application.Current.RootVisual.Dispatcher;
            }
            catch (Exception e)
            {
                throw new InvalidOperationException("The first time SmartDispatcher is used must be from a user interface thread. Consider having the application call Initialize, with or without an instance.", e);
            }

            if (_instance == null)
            {
                throw new InvalidOperationException("Unable to find a suitable Dispatcher instance.");
            }
        }

        /// <summary>
        /// Initializes the SmartDispatcher system, attempting to use the
        /// RootVisual of the plugin to retrieve a Dispatcher instance.
        /// </summary>
        public static void Initialize()
        {
            if (_instance == null)
            {
                RequireInstance();
            }
        }

        /// <summary>
        /// Initializes the SmartDispatcher system with the dispatcher
        /// instance.
        /// </summary>
        /// <param name="dispatcher">The dispatcher instance.</param>
        public static void Initialize(Dispatcher dispatcher)
        {
            if (dispatcher == null)
            {
                throw new ArgumentNullException("dispatcher");
            }

            _instance = dispatcher;

            if (_designer == null)
            {
                _designer = DesignerProperties.IsInDesignTool;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public static bool CheckAccess()
        {
            if (_instance == null)
            {
                RequireInstance();
            }

            return _instance.CheckAccess();
        }

        /// <summary>
        /// Executes the specified delegate asynchronously on the user interface
        /// thread. If the current thread is the user interface thread, the
        /// dispatcher if not used and the operation happens immediately.
        /// </summary>
        /// <param name="a">A delegate to a method that takes no arguments and 
        /// does not return a value, which is either pushed onto the Dispatcher 
        /// event queue or immediately run, depending on the current thread.</param>
        public static void BeginInvoke(Action a)
        {
            if (_instance == null)
            {
                RequireInstance();
            }

            // If the current thread is the user interface thread, skip the
            // dispatcher and directly invoke the Action.
            if (_instance.CheckAccess() || _designer == true)
            {
                a();
            }
            else
            {
                _instance.BeginInvoke(a);
            }
        }
    }
}

1
这是非常棒的东西,解决了我的偏头痛问题。 - cjones26

6
如果您使用MVVM light toolkit,您可以在Extras dll中的Galasoft.MvvmLight.Threading命名空间中使用DispatcherHelper类。它检查访问并使用静态属性来调度程序,类似于SmartDispatcher。
在您的App.xaml.cs启动事件中调用:
DispatcherHelper.Initialize();

然后在任何需要使用调度程序的地方使用:

   DispatcherHelper.CheckBeginInvokeOnUI(() => // do stuff; );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接