在工作线程中创建对象并绑定到它们

发布于 2024-10-31 20:44:15 字数 2386 浏览 0 评论 0原文

我在 C# / WPF / .NET 4.0 中遇到跨线程操作问题。

情况:

当用户单击按钮时,我必须创建一个对象树,然后绑定到该树。因为创建需要很长时间(子对象是递归实例化的),所以我使用了Thread/BackgroundWorker/Task来防止UI冻结。

问题

绑定到对象树时,我收到 XamlParserException(必须在与 DependencyObject 相同的线程上创建 DependencySource)。

我明白这个问题,但是如何解决呢?我无法在 UI 线程上创建对象树,因为这会冻结 UI。但我也无法在另一个线程上创建对象树,因为那样我就无法绑定到它。

有没有办法将对象“编组”到 UI 线程?

事件处理程序代码(在 UI 线程上执行)

    private void OnDiff(object sender, RoutedEventArgs e)
    {

        string path1 = this.Path1.Text;
        string path2 = this.Path2.Text;

        // Some simple UI updates.
        this.ProgressWindow.SetText(string.Format(
            "Comparing {0} with {1}...",
            path1, path2));

        this.IsEnabled = false;
        this.ProgressWindow.Show();
        this.ProgressWindow.Focus();

        // The object tree to be created.
        Comparison comparison = null;

        Task.Factory.StartNew(() =>
        {

            // May take a few seconds...
            comparison = new Comparison(path1, path2);

        }).ContinueWith(x =>
        {


            // Again some simple UI updates.
            this.ProgressWindow.SetText("Updating user interface...");
            this.DiffView.Items.Clear();
            this.Output.Items.Clear();

            foreach (Comparison diffItem in comparison.Items)
            {
                this.DiffView.Items.Add(diffItem);

                this.AddOutput(diffItem);
            }

            this.Output.Visibility = Visibility.Visible;

            this.IsEnabled = true;
            this.ProgressWindow.Hide();

        }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());

    }

绑定示例

            <DataGrid.Columns>
                <DataGridTemplateColumn CellTemplate="{StaticResource DataGridIconCellTemplate}"/>
                <DataGridTextColumn Header="Status" Binding="{Binding Path=ItemStatus}"/>
                <DataGridTextColumn Header="Type"   Binding="{Binding Path=ItemType}"/>
                <DataGridTextColumn Header="Path"   Binding="{Binding Path=RelativePath}"
                                    Width="*"/>
            </DataGrid.Columns>

您好, 多米尼克

I've a problem with cross-thread operations in C# / WPF / .NET 4.0.

The situation:

I have to create a object tree when the user clicks a button and then bind to the tree. Because the creation takes a long time (child objects are recursively instantiated), I used a Thread/BackgroundWorker/Task to prevent the UI from freezing.

The problem:

I get a XamlParserException (Must create DependencySource on same Thread as the DependencyObject) when binding to the object tree.

I understand the problem, but how can it be fixed? I can't create the object tree on the UI thread because this will freeze the UI. But I also can't create the object-tree on another thread because then I'm not able to bind to it.

Is there a way to 'marshal' objects to the UI thread?

The Event Handler Code (executed on UI thread)

    private void OnDiff(object sender, RoutedEventArgs e)
    {

        string path1 = this.Path1.Text;
        string path2 = this.Path2.Text;

        // Some simple UI updates.
        this.ProgressWindow.SetText(string.Format(
            "Comparing {0} with {1}...",
            path1, path2));

        this.IsEnabled = false;
        this.ProgressWindow.Show();
        this.ProgressWindow.Focus();

        // The object tree to be created.
        Comparison comparison = null;

        Task.Factory.StartNew(() =>
        {

            // May take a few seconds...
            comparison = new Comparison(path1, path2);

        }).ContinueWith(x =>
        {


            // Again some simple UI updates.
            this.ProgressWindow.SetText("Updating user interface...");
            this.DiffView.Items.Clear();
            this.Output.Items.Clear();

            foreach (Comparison diffItem in comparison.Items)
            {
                this.DiffView.Items.Add(diffItem);

                this.AddOutput(diffItem);
            }

            this.Output.Visibility = Visibility.Visible;

            this.IsEnabled = true;
            this.ProgressWindow.Hide();

        }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());

    }

Example Binding

            <DataGrid.Columns>
                <DataGridTemplateColumn CellTemplate="{StaticResource DataGridIconCellTemplate}"/>
                <DataGridTextColumn Header="Status" Binding="{Binding Path=ItemStatus}"/>
                <DataGridTextColumn Header="Type"   Binding="{Binding Path=ItemType}"/>
                <DataGridTextColumn Header="Path"   Binding="{Binding Path=RelativePath}"
                                    Width="*"/>
            </DataGrid.Columns>

Greetings,
Dominik

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

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

发布评论

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

评论(2

七颜 2024-11-07 20:44:15

您可以在工作线程上创建图标,但在 UI 线程上使用它之前需要冻结它:

            var icon = Imaging.CreateBitmapSourceFromHIcon(
              sysicon.Handle,
              System.Windows.Int32Rect.Empty,
              System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
            icon.Freeze();
            Dispatcher.Invoke(new Action(() => this.Icon = icon));

You can create the icon on the worker thread, but you need to freeze it before you use it on the UI thread:

            var icon = Imaging.CreateBitmapSourceFromHIcon(
              sysicon.Handle,
              System.Windows.Int32Rect.Empty,
              System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
            icon.Freeze();
            Dispatcher.Invoke(new Action(() => this.Icon = icon));
苍风燃霜 2024-11-07 20:44:15

您以前使用过 Dispatcher.Invoke 方法吗?我的理解是,您可以在单独的线程(例如任务)上执行一个长时间运行的进程,并使用 Dispatcher 通过委托来更新 UI 线程上的控件。

private void DoWork()
{
    // Executed on a separate thread via Thread/BackgroundWorker/Task

    // Dispatcher.Invoke executes the delegate on the UI thread
    Dispatcher.Invoke(new System.Action(() => SetDatesource(myDatasource)));
}

Have you used the Dispatcher.Invoke method before? My understanding is you can have a long running process executing on a separate thread (eg Task) and use the Dispatcher to update the controls on the UI thread using a delegate.

private void DoWork()
{
    // Executed on a separate thread via Thread/BackgroundWorker/Task

    // Dispatcher.Invoke executes the delegate on the UI thread
    Dispatcher.Invoke(new System.Action(() => SetDatesource(myDatasource)));
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文