多线程时 WPF 无法更新状态
Posted
技术标签:
【中文标题】多线程时 WPF 无法更新状态【英文标题】:WPF failing to update state when multi-threading 【发布时间】:2021-10-17 19:14:39 【问题描述】:使用多线程和 WPF 运行问题。我真的不知道自己在做什么,而且通常的 *** 答案也不起作用。
首先,通过以下方式创建了一堆 WPF 窗口:
var thread = new Thread(() =>
var bar = new MainWindow(command.Monitor, _workspaceService, _bus);
bar.Show();
System.Windows.Threading.Dispatcher.Run();
);
thread.Name = "Bar";
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
在生成的窗口的 ctor 中,创建了一个视图模型,并在应该更改视图模型的位置监听一个事件。
this.DataContext = new BarViewModel();
// Listen to an event propagated on main thread.
_bus.Events.Where(@event => @event is WorkspaceAttachedEvent).Subscribe(observer =>
// Refresh contents of viewmodel.
(this.DataContext as BarViewModel).SetWorkspaces(monitor.Children);
);
视图模型状态修改如下:
public void SetWorkspaces(IEnumerable<Workspace> workspaces)
Application.Current.Dispatcher.Invoke((Action)delegate
this.Workspaces.Clear(); // this.Workspaces is an `ObservableCollection<Workspace>`
foreach (var workspace in workspaces)
this.Workspaces.Add(workspace);
this.OnPropertyChanged("Workspaces");
);
问题是访问Application.Current.Dispatcher
导致NullReferenceException
。生成窗口的方式有问题吗?
【问题讨论】:
我真的不知道我在做什么 -> 你为什么选择在一个单独的线程中构建窗口?所有 UI 工作都应在 UI 线程上完成。 该应用程序的其余部分是一个标准的 .NET 核心控制台应用程序,我需要一种以编程方式生成 WPF 窗口的方法。我有一个业务逻辑所在的主线程,所以我这样做只是因为它似乎完成了打开窗口的工作。有没有更好的方法来生成窗口? 【参考方案1】:应用程序的其余部分是标准的 .NET 核心控制台应用程序,我需要一个 以编程方式生成 WPF 窗口的方法
生成窗口的方式有问题吗?
是的。
在 WPF 应用程序中,您有一个处理所有 UI 工作的主线程(也称为 UI 线程)。尝试从其他线程执行 UI 工作可能会导致异常。
听起来您的入口点不是 WPF 应用程序,而是控制台应用程序,而您正试图从该控制台应用程序生成窗口。这是行不通的。
您需要像平常一样创建一个 WPF 项目,并将其作为您的主要入口点。
另外,不要在应该使用 Task
s 的地方使用 Thread
对象。
【讨论】:
问题在于显示 WPF 窗口更多的是一个附带功能,并且可能存在没有显示窗口的情况。使用上面的代码,我可以生成具有初始状态的窗口,但是从不同的线程更新该状态似乎很棘手。还研究了使用SynchronizationContext
,但SynchronizationContext.Current
在更新状态时是null
。我很欣赏关于Task
s 的提示!
这是一个有趣的用例。我相信您可以通过创建App
对象和Run
ning 来实现它而无需进行重大重构。试试这些解决方案:***.com/a/43388522/6104191***.com/a/23298284/6104191【参考方案2】:
问题是,您不会有Application.Current
实例。您正在为您的窗口启动线程,每个线程/窗口都有自己的调度程序。所以最简单的解决方案是将 MainWindow 的 Dispatcher 传递给 ViewModel 并使用它而不是 Application.Current.Dispatcher
。一个快速且非常肮脏的解决方案:
static void Main(string[] args)
do
switch (Console.ReadLine())
case "n":
StartNewWindowOnOwnThread();
break;
default:
return;
while (true);
private static void StartNewWindowOnOwnThread()
var t = new Thread(() =>
var w = new MainWindow();
w.Show();
System.Windows.Threading.Dispatcher.Run();
);
t.SetApartmentState(ApartmentState.STA);
t.Start();
MainWindow的ViewModel:
class MainWindowModel : INotifyPropertyChanged
private readonly Dispatcher dispatcher;
private readonly Timer timer;
private int count;
public event PropertyChangedEventHandler PropertyChanged;
internal MainWindowModel(Dispatcher dispatcher)
this.dispatcher = dispatcher;
//Simulate background activity on another thread
timer = new Timer(OnTimerTick, null, 1000, 1000);
public string ThreadId => $"Thread dispatcher.Thread.ManagedThreadId";
public int Count
get return count;
set
count = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
private void OnTimerTick(object state)
dispatcher.BeginInvoke(new Action(() => Count++));
主窗口:
public partial class MainWindow : Window
public MainWindow()
InitializeComponent();
DataContext = new MainWindowModel(Dispatcher);
private void Button_Click(object sender, RoutedEventArgs e)
Dispatcher.InvokeShutdown();
XAML - 片段:
<Grid>
<StackPanel Orientation="Vertical">
<Button Content="Close" Click="Button_Click"/>
<TextBlock Text="Binding ThreadId"/>
<TextBlock Text="Binding Count"/>
</StackPanel>
</Grid>
【讨论】:
哇,感谢您的回复。我已经尝试完全实现您的方法(尽管基础变量当然不同),并且在通过SetWorkspaces(...)
更新视图模型时遇到'The calling thread cannot access this object because a different thread owns it.'
。如果您决定看一下,请将回购公开。有问题的文件是this,可以通过按例如 ALT+4 来复制
在 .NET core 4+ 中发现一条评论提到“在更新 VM 中的 UI 支持属性时,您不再需要担心正确的调度程序”。听起来这可能是我的确切问题,所以我会尝试修改版本,看看是否有什么作用。 ***.com/questions/9732709/…
@L.Berger 我假设,事件处理程序是从另一个线程调用的。使用 Dispatcher 调用 RefreshViewModel。将Dispatcher.BeginInvoke(new Action(() => RefreshViewModel(monitor));
放入Subscribe(observer => ...
。
没错,Subscribe
回调是在另一个线程上调用的。通过Dispatcher
的调用位于视图模型here 中。最终升级到 .NET 5,但这个该死的错误仍在弹出
终于似乎有所进展,但仍然感到困惑。如果在视图模型中,我将状态从ObservableCollection<Workspace>
更改为string
(仅用于调试)并在Subscribe
回调中改变它,它正在成功更新。出于某种原因,ObservableCollection
特有的某些东西似乎导致了问题以上是关于多线程时 WPF 无法更新状态的主要内容,如果未能解决你的问题,请参考以下文章
“调用线程无法访问此对象,因为不同的线程拥有它”从 WPF 中的不同线程更新 UI 控件时出现错误