【问题标题】:How do I edit grid in WPF UI from the codebehind (C#) without UI freezing? [duplicate]如何在不冻结 UI 的情况下从代码隐藏 (C#) 编辑 WPF UI 中的网格? [复制]
【发布时间】:2020-04-22 22:05:46
【问题描述】:

我想要运行相当简单的代码来编辑 UI。我知道如果我在主线程中运行 UI 会冻结,所以我一直在努力在单独的线程中运行它,但这不起作用。这是我想要发生的事情: 用户使用 UI 填充两个列表。一个列表跟踪屏幕上的内容,另一个跟踪稍后需要在屏幕上显示的内容。当用户单击某个按钮时,它会清除第一个列表和 UI。然后,我使用第二个列表再次在 UI 上显示每个项目,并在每个对象返回之间有一定的时间长度。这是一个简化的形式:

myList = [/*items to be displayed*/];
myMessages = [/*items currently displayed*/]
private void ButtonAnimationRun(object sender, RoutedEventArgs e)
{
    Thread thread = new Thread(() =>
    {
        Run();
    });
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();

}
public void Run()
{
    foreach (Message message in myList)
    {
        Thread.Sleep(message.Time * 1000);
        CreateText(message.Text);
    }          
}
public void CreateText(string text)
{
    Dispatcher.Invoke(() => myGrid.Children.Add(myMessages[myMessages.Count - 1]));
    // text is added to the child of myGrid here
    Dispatcher.Invoke(() => myScrollBar.ScrollToBottom());
}

这不是我的全部代码,而是与我的问题相关的代码。 如果我使用 await 或 tasks,我会收到一个 Apartment State 错误。 使用上面的代码,我收到此错误:“调用线程无法访问此对象,因为不同的线程拥有它。”在第一个 Invoke 的行上,无论有没有 Invoke 我仍然得到它。 当我注释掉前一个时,第二个 Invoke 工作得很好,但是,或者当然,我没有在 UI 上显示任何内容,因为这是将 UI 元素添加到 UI 的代码。 我已经搜索了 stackoverflow,我找到的所有答案都会导致其他人突然出现或禁止我的应用程序的功能。 如果您需要更多信息,我可以提供。

【问题讨论】:

  • 具有合理数据类型的代码应该在不释放 UI 的情况下表现良好...请edit 代码发布minimal reproducible example。特别是,您不应该需要 STA 来显示代码并使用 myList 的字符串列表。如果 myList 实际上是可见控件的列表,则需要将其包含在代码示例中。 CreateText 也没有使用它的参数......并且由于某种原因有 2 个 .Invoke 调用 - 你也不需要它来获取正确的样本。

标签: c# wpf multithreading


【解决方案1】:

你得到这个异常的原因是你使用了错误的Dispatcher

在 WPF 中,每个线程都有自己的Dispatcher。所有UIElement 对象也派生自DispatcherObject。每个DispatcherObject 都有一个调度程序关联,这意味着它们与特定的Dispatcher 相关联——Dispatcher 是在其上创建DispatcherObject(或UIElement)的线程的Dispatcher。这就是为什么这种亲和性也称为线程亲和性。

现在,您了解如何触发跨线程异常“调用线程无法访问此对象,因为不同的线程拥有它。”:访问DispatcherObject 例如Grid 来自错误的线程或通过错误的Dispatcher

要获得正确的Dispatcher,即与DispatcherObject 关联的Dispatcher,您可以访问DispatcherObject.Dispatcher 属性:

var dispatcher = myGrid.Dispatcher;

由于大多数时候DispatcherObjects 是在应用程序的(主)UI 线程上创建的,因此您也可以使用它的Dispatcher。你可以通过static属性访问UI线程Dispatcher

var dispatcher = Application.Current.Dispatcher;

如果您有一个长时间运行的调度程序作业,请考虑在后台异步执行它(或以低于默认值的优先级)。这也有助于防止 UI 变得迟缓:

myGrid.Dispatcher.InvokeAsync(() => myScrollBar.ScrollToBottom(), DispatcherPriority.Background);

如果您手动创建一个新的 UI 线程(我猜您有充分的理由这样做),您可能希望通过调用 Dispatcher.Run 来启动 Dispatcher 循环(或框架)。考虑使用Task.Run 代替Thread 和异步Task.Delay 代替阻塞Thread.Sleep


再次阅读您的问题后,我了解到您创建此线程只是因为您想防止 UI 冻结。我也想到,您创建线程的方式可能是因为您不了解它。如果是这种情况,我强烈建议实施以下解决方案,这也可能解决您的问题:

private async void ButtonAnimationRun(object sender, RoutedEventArgs e)
{
  foreach (Message message in myList)
  {
    await Task.Delay(message.Time * 1000);
    await CreateTextAsync(message.Text);
  }          
}

public async Task CreateTextAsync(string text)
{
  // Do the heavy work asynchronously on a background thread
  await Task.Run(() =>
    {
      Application.Current.Dispatcher.InvokeAsync(() => myGrid.Children.Add(myMessages[myMessages.Count - 1]), DispatcherPriority.Background);

      // text is added to the child of myGrid here

      Application.Current.Dispatcher.InvokeAsync(() => myScrollBar.ScrollToBottom(), DispatcherPriority.Background);
    }
}

【讨论】:

  • 我已经尝试过myGrid.DispatcherApplication.Current.DispatcherInvokeAsync(),但我仍然得到“调用线程无法访问此对象,因为另一个线程拥有它。”全部出错。
  • myGrid 是在哪里创建的,在主 UI 线程还是您的新 STA 线程上?
  • 同意关于控件创建位置的评论。也应该使用BeginInvoke 而不是Invoke。没有理由阻止。
  • myGrid 在 XAML 文件中创建。
  • @Hans - 他指的是创建它的线程。控件/窗口的构造和初始化决定了它使用哪个 STA 线程。这看起来像你真正的问题,除非你使用他提到的更好的设计。
【解决方案2】:

我很想进一步简化 BionicCode 的 answer,完全消除对 Dispatcher 的任何使用。我也会摆脱Task.Run,因为没有繁重的工作会冻结已发布代码中的 UI。 async-await 的美妙之处在于,在每个await 之后,我们都回到了 UI 线程,根本不需要做任何事情。

private async void ButtonAnimationRun(object sender, RoutedEventArgs e)
{
    foreach (Message message in myList)
    {
        await Task.Delay(message.Time * 1000);
        CreateText(message.Text);
    }
}

public void CreateText(string text)
{
    myGrid.Children.Add(myMessages[myMessages.Count - 1]);
    // text is added to the child of myGrid here
    myScrollBar.ScrollToBottom();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-07
    • 2011-12-27
    • 2012-01-05
    • 2016-09-15
    • 2014-07-14
    相关资源
    最近更新 更多