在开始之前,我要告诉你,我仍然只是在学习(通过做)线程,而且我认为我错过了这个难题的一些重要部分。
但是,我遇到了一个奇怪的问题。我正在编写的应用程序(请不要评论代码有多糟糕)可以工作。但是,最初它没有更新 UI (WPF)。因此,根据各个论坛上的点点滴滴,我进行了摆弄,以便这项工作(目前只是在文件夹结构中阅读)在新线程中完成其工作。在此过程中,我尝试使用 ConcurrentQueue 在 Worker 和 UI 之间传递消息。这是通过轮询来实现的(我不想这样做,因为它有时会使用相当多的 CPU 来定期轮询,但其他轮询会持续很长时间而没有改变)。因此,我发现 ObservableConcurrentQueue 作为 ConcurrentQueue 的包装器,并设置了(我认为)应该是事件订阅的内容。 (在阅读时,似乎有一千种不同的方法来给消息猫换皮,这似乎是最/最简单的一种)
它应该有效,但我现在得到“System.InvalidOperationException:'调用线程无法访问此对象,因为另一个线程拥有它。'当FolderCount 增加时。
我不确定如何或为什么一个不同的线程现在突然拥有它,而它以前是正常的。我创建的唯一线程是工作线程,它(是?)成功通过并发队列发送消息,并且不应该运行有问题的代码。根据我的解释,其他所有内容都应该在同一个线程上(当我在工作线程中创建订阅时,它给了我同样的错误,但将其切换到 MainWindow 类/线程应该已经修复了它?)。
下面是相关的代码,应该显示我做错了什么。 我毫不怀疑我做得“糟糕”,甚至可能尝试混合方法。
{
public void SearchFolder(string path)
{
Task.Run(() => { SFolders(path); });
}
private void SFolders(string path)
{
if (Directory.Exists(path)) {
_pm.Add(ProcessMessages.FolderCountAdd); // THIS IS WHERE IT CHUCKS A HISSY FIT
// OTHER STUFF - rest of routine
}
// MORE STUFF
}
}
public partial class MainWindow : Window
{
public MainWindow mw = null;
public ProcessMessages pm = null;
public Searching searching = null;
public MainWindow()
{
InitializeComponent();
mw = this;
pm = new ProcessMessages();
searching = new Searching(pm, this);
pm._queue.ContentChanged += OnObservableConcurrentQueueContentChanged;
}
private void OnObservableConcurrentQueueContentChanged(object sender, NotifyConcurrentQueueChangedEventArgs<int> args)
{
// Item Added
if (args.Action == NotifyConcurrentQueueChangedAction.Enqueue)
{
int message;
pm.Fetch(out message);
while (message > 0)
{
switch (message)
{
case ProcessMessages.FolderCountAdd: { FolderCount++; break; }
case ProcessMessages.FileCountAdd: { FileCount++; break; }
}
pm.Fetch(out message);
}
}
}
public class ProcessMessages
{
public ObservableConcurrentQueue<int> _queue = new ObservableConcurrentQueue<int>();
public int Count => _queue.Count;
public void Add(int message)
{
if (message == null) throw new ArgumentNullException(nameof(message));
_queue.Enqueue(message);
}
public bool Fetch(out int message)
{
return _queue.TryDequeue(out message);
}
}
}
在我看来,事件处理程序和 UI 应该都在同一个线程中运行,所以我不确定发生了什么。
提前感谢任何能够解释问题所在的人/每个人(即使从根本上来说这是错误的)。
那么,您本质上希望后台线程向 UI 发出信号,表明它应该更新?
最棒的是已经有人排队了!我所知道的所有 UI 框架都是围绕消息队列工作的。您可以通过调用 Dispatcher.BeginInvoke 或直接调用来将消息放入此队列中。这将在稍后的时间调用 UI 线程上提供的委托。
但是您不需要立即取消投票。如果更新频繁,则仅使用计时器检查更新并以批处理操作更新 UI 可能会更有效。每 100 毫秒左右轮询一次应该只使用少量的 CPU。
我从未听说过 ObservableConcurrentQueue,但阅读您正在使用的任何类型的文档通常很重要,尤其是在线程安全方面。它被记录为线程安全的,但没有提及任何与 UI 线程的同步。有些类允许您有选择地指定同步对象,例如调度程序或 ISynchronizedInvoke,但这些类往往是特定于 UI 框架的。由于 ObservableConcurrentQueue 似乎对任何 UI 框架都没有任何依赖,因此它可能不支持任何此类同步。 在使用队列在非 UI 线程之间进行通信的一般情况下,您可能应该看看
BlockingCollection。这让工作线程简单地围绕 .GetConsumingEnumerable()
循环,并且当没有要处理的项目时,工作线程将自动阻塞,无需轮询。我还会查看
channels和 Dataflow 以获取有关线程的更高级别的抽象。您还应该熟悉 async/await,因为这是大多数线程任务的现代方法。 在我看来,事件处理程序和 UI 应该都在同一个线程中运行,所以我不确定发生了什么。
这很容易验证。只需在事件处理程序中放置一个断点并检查它正在哪个线程上运行。
我还在学习(通过做)线程
危险线程安全不太适合“边做边学”。编写“几乎”始终有效的代码非常容易。如果不幸的话,它会在某些关键操作期间默默地失败,从而导致不正确的结果和非常昂贵的清理费用,可能还包括诉讼。您需要熟悉