【问题标题】:Threading issue with Telerik RadDockingTelerik RadDocking 的线程问题
【发布时间】:2013-10-08 00:37:42
【问题描述】:

我所咨询的公司有一个特定的业务要求,即某些 WPF Windows 的每个实例都必须有自己的 UI 线程,并且在首次加载应用程序时不共享 .NET Framework 创建的默认 UI 线程。

从编码的角度来看,这很容易实现并且运行良好,直到在 xaml 中引入 Telerik RadDocking 控件。我直接从示例代码中复制并粘贴了 xaml 表单 Telerik 的 RadDocking 示例,而没有对其进行修改。当应用程序启动时,WindowWithTelerikDockingFromExample [似乎] 的两个实例首先加载没有问题,事实上,窗口的第二个实例(标题为“单独 UI 线程上的窗口......”)是可操作的并且工作正常,“MainWindow”也是如此.直到你激活第二个窗口,然后激活主窗口,然后再切换回第二个窗口,才会抛出以下异常:

“调用线程无法访问此对象,因为不同的线程拥有它。”

定位源

'c:\TB\117\WPF_Scrum\Release_WPF\Sources\Development\Controls\Docking\Docking\Parts\AutoHideArea.cs'. Checksum: MD5 {3e 1e cd 2a 97 89 30 7e c9 1c 28 c2 28 13 aa e9}
The file 'c:\TB\117\WPF_Scrum\Release_WPF\Sources\Development\Controls\Docking\Docking\Parts\AutoHideArea.cs' does not exist.
Looking in script documents for 'c:\TB\117\WPF_Scrum\Release_WPF\Sources\Development\Controls\Docking\Docking\Parts\AutoHideArea.cs'...
Looking in the projects for 'c:\TB\117\WPF_Scrum\Release_WPF\Sources\Development\Controls\Docking\Docking\Parts\AutoHideArea.cs'.
The file was not found in a project.
Looking in directory 'C:\Program Files\Microsoft Visual Studio 10.0\VC\crt\src\'...
Looking in directory 'C:\Program Files\Microsoft Visual Studio 10.0\VC\atlmfc\src\mfc\'...
Looking in directory 'C:\Program Files\Microsoft Visual Studio 10.0\VC\atlmfc\src\atl\'...
Looking in directory 'C:\Program Files\Microsoft Visual Studio 10.0\VC\atlmfc\include\'...
The debug source files settings for the active solution indicate that the debugger will not ask the user to find the file: c:\TB\117\WPF_Scrum\Release_WPF\Sources\Development\Controls\Docking\Docking\Parts\AutoHideArea.cs.
The debugger could not locate the source file 'c:\TB\117\WPF_Scrum\Release_WPF\Sources\Development\Controls\Docking\Docking\Parts\AutoHideArea.cs'.

这是我的代码:

App.xaml.cs:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        this.ShutdownMode = System.Windows.ShutdownMode.OnLastWindowClose;

        // Init the application's main window...
        var mainWindow = new WindowWithTelerikDockingFromExample();
        mainWindow.Title = "Main Window";
        this.MainWindow = mainWindow;
        mainWindow.Show();

        // init another instance of the window with the telerik docking, on a seperate UI thread...
        var thread = new Thread(() =>
        {
            SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
            var window2 = new WindowWithTelerikDockingFromExample();
            window2.Title = "Window on seperate UI Thread...";
            window2.Show();
            System.Windows.Threading.Dispatcher.Run();
            window2.Closed += (s2, e2) =>
                {
                    window2.Dispatcher.InvokeShutdown();
                };

        });

        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();

        base.OnStartup(e);
    }

}

WindowWithTelerikDockingFromExample.xaml:

<Window x:Class="TelerikDockingThreadIssueExample.WindowWithTelerikDockingFromExample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
        Title="Window with xaml copy and pasted from Telerik example" Height="300" Width="300">
    <Grid>
        <telerik:RadDocking   BorderThickness="0" Padding="0">
            <telerik:RadDocking.DocumentHost>
                <telerik:RadSplitContainer>
                    <telerik:RadPaneGroup>
                        <telerik:RadDocumentPane Header="Document 1" Title="Document 1" />
                    </telerik:RadPaneGroup>
                </telerik:RadSplitContainer>
            </telerik:RadDocking.DocumentHost>

            <telerik:RadSplitContainer InitialPosition="DockedLeft">
                <telerik:RadPaneGroup>
                    <telerik:RadPane Header="Pane Left 1" IsPinned="False">
                        <TextBlock Text="Pane Left 1" />
                    </telerik:RadPane>
                    <telerik:RadPane Header="Pane Left 2" IsPinned="False">
                        <TextBlock Text="Pane Left 2" />
                    </telerik:RadPane>
                    <telerik:RadPane Header="Pane Left 3" IsPinned="False">
                        <TextBlock Text="Pane Left 3" />
                    </telerik:RadPane>
                    <telerik:RadPane Header="Pane Left 4" IsPinned="False">
                        <TextBlock Text="Pane Left 4" />
                    </telerik:RadPane>
                </telerik:RadPaneGroup>
            </telerik:RadSplitContainer>

            <telerik:RadSplitContainer InitialPosition="DockedRight">
                <telerik:RadPaneGroup>
                    <telerik:RadPane Header="Pane Right 1" IsPinned="False">
                        <TextBlock Text="Pane Right 1" />
                    </telerik:RadPane>
                </telerik:RadPaneGroup>
            </telerik:RadSplitContainer>

            <telerik:RadSplitContainer InitialPosition="DockedBottom">
                <telerik:RadPaneGroup>
                    <telerik:RadPane Header="Pane Bottom 1" IsPinned="False">
                        <TextBlock Text="Pane Bottom 1" />
                    </telerik:RadPane>
                </telerik:RadPaneGroup>
            </telerik:RadSplitContainer>
        </telerik:RadDocking>
    </Grid>
</Window>

有什么想法吗?

【问题讨论】:

    标签: wpf multithreading telerik dispatcher raddocking


    【解决方案1】:

    由于该公司是 Telerik 的付费客户,我可以使用他们的帐户通过支持票向 Telerik 发送信息。以下是我发送给他们的摘录。这有点类似于我最初在 StackOverflow 上发布的问题,但是,我更进一步,反编译了他们的 RadDocking DLL,并就他们需要做什么来解决这个问题提出了建议。我很高兴地报告他们回答了这个问题:

    我们有一个特定的业务要求,即 WPF Windows 的特定实例必须每个都有自己的 UI 线程,而不是使用加载应用程序时创建的默认 UI 线程。根据我们的经验,从编码的角度来看,这样做相对容易完成,直到我们将 Telerik RadDocking 控件引入组合中。 我附上了一个包含两个主要文件的简单 VS 解决方案的 zip 文件。其中一个是 app.xaml.cs,另一个是名为 WindowWithTelerikDockingFromExample.xaml 的窗口。 Window 中的 xaml 是直接从您的 WPF Docking 示例中复制和粘贴的,没有任何更改。 app.xaml.cs 文件有一种方法:OnStartup。 OnStartup 方法做了两件事: 1. 它首先创建一个 Window 实例并将应用程序 MainWindow 设置为该实例。 2. 然后它创建另一个 Window 实例,但在不同的线程下。 Window 的两个实例启动没有问题并且可以运行。直到用户激活其中一个,然后激活另一个,然后在抛出以下异常时再次激活另一个: “调用线程无法访问此对象,因为不同的线程拥有它。”

    我们认为问题出在 Telerik.Windows.Controls.Docking.dll 中的 AutoHideArea 类中。我们自己使用免费的 Telerik JustDecompile 工具来反编译 theTelerik.Windows.Controls.Docking.dll,以帮助调查可能发生问题的位置。 AutoHideArea 中的“OnLoaded”方法连接一个“OnApplicationDeactivated”事件,如下所示:

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        this.isLoaded = true;
        base.SelectedIndex = -1;
        if (!BrowserInteropHelper.IsBrowserHosted)
        {
            WeakReference weakReference = new WeakReference(this);
            Window window = Window.GetWindow(this);
            if (window != null)
            {
                window.SizeChanged += new SizeChangedEventHandler((objects, SizeChangedEventArgs a) => AutoHideArea.OnWindowEventOccured(weakReference));
                window.LocationChanged += new EventHandler((object s, EventArgs a) => AutoHideArea.OnWindowEventOccured(weakReference));
            }
            if (Application.Current != null)
            {
                Application.Current.Deactivated += new EventHandler((object s, EventArgs a) => AutoHideArea.OnApplicationDeactivated(weakReference));
            }
        }
    }
    

    如果您遵循 OnApplicationDeactivated 方法,您会注意到它调用了“area.CloseImmediately()”方法,如下所示:

    private static void OnApplicationDeactivated(WeakReference target)
    {
        AutoHideArea autoHideArea;
        if (target.IsAlive)
        {
            autoHideArea = target.Target as AutoHideArea;
        }
        else
        {
            autoHideArea = null;
        }
        AutoHideArea area = autoHideArea;
        if (area != null)
        {
            area.CloseImmediately();
        }
    }
    

    如果您一直遵循该方法,您会注意到它设置 base.SelectedIndex = -1,如下所示:

    private void CloseImmediately()
    {
        this.OnLayoutChangeStarted();
        base.SelectedIndex = -1;
        this.OnLayoutChangeEnded();
    }
    

    我们建议将方法更改为如下所示:

    private void CloseImmediately()
         {
             if (!System.Windows.Threading.Dispatcher.CurrentDispatcher.CheckAccess())
             {
                 System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke(new Action(CloseImmediately),null);
                 return;
             }
             this.OnLayoutChangeStarted();
             base.SelectedIndex = -1;
             this.OnLayoutChangeEnded();
         }
    

    以下是 Telerik 的回应:

    感谢您在 RadDocking 中向我们报告此异常。这 控制未在多线程环境中测试,我们不知道 的此类问题。问题记录在我们的 PITS 中 - http://www.telerik.com/support/pits.aspx#/public/wpf/15920。我们会 查看我们即将发布的版本的例外情况,我们将考虑 您对修复的建议。

    感谢您的反馈。我很高兴更新您的 Telerik 积分 也是。

    问候,

    乔治 Telerik

    试用 TELERIK 的最新产品 - 适用于 WPF 的 EQATEC 应用程序分析。 了解您的用户在您的应用程序中使用(或不使用)哪些功能。 了解你的听众。更好地瞄准它。明智地发展。

    【讨论】:

    • 不幸的是,该错误仍然存​​在并且“未计划”,因此我们不会在 2013 Q3 SP1 中修复 ..
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-26
    相关资源
    最近更新 更多