C# 线程之间的消息队列和一般线程问题

问题描述 投票:0回答:1

在开始之前,我要告诉你,我仍然只是在学习(通过做)线程,而且我认为我错过了这个难题的一些重要部分。

但是,我遇到了一个奇怪的问题。我正在编写的应用程序(请不要评论代码有多糟糕)可以工作。但是,最初它没有更新 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 应该都在同一个线程中运行,所以我不确定发生了什么。

提前感谢任何能够解释问题所在的人/每个人(即使从根本上来说这是错误的)。

c# multithreading concurrent-queue
1个回答
0
投票

那么,您本质上希望后台线程向 UI 发出信号,表明它应该更新?

最棒的是已经有人排队了!我所知道的所有 UI 框架都是围绕消息队列工作的。您可以通过调用 Dispatcher.BeginInvoke 或直接调用来将消息放入此队列中。这将在稍后的时间调用 UI 线程上提供的委托。

但是您不需要立即取消投票。如果更新频繁,则仅使用计时器检查更新并以批处理操作更新 UI 可能会更有效。每 100 毫秒左右轮询一次应该只使用少量的 CPU。

我从未听说过 ObservableConcurrentQueue,但阅读您正在使用的任何类型的文档通常很重要,尤其是在线程安全方面。它被记录为线程安全的,但没有提及任何与 UI 线程的同步。有些类允许您有选择地指定同步对象,例如调度程序或 ISynchronizedInvoke,但这些类往往是特定于 UI 框架的。由于 ObservableConcurrentQueue 似乎对任何 UI 框架都没有任何依赖,因此它可能不支持任何此类同步。 在使用队列在非 UI 线程之间进行通信的一般情况下,您可能应该看看

BlockingCollection

。这让工作线程简单地围绕 .GetConsumingEnumerable() 循环,并且当没有要处理的项目时,工作线程将自动阻塞,无需轮询。我还会查看

channels
Dataflow 以获取有关线程的更高级别的抽象。您还应该熟悉 async/await,因为这是大多数线程任务的现代方法。

在我看来,事件处理程序和 UI 应该都在同一个线程中运行,所以我不确定发生了什么。

这很容易验证。只需在事件处理程序中放置一个断点并检查它正在哪个线程上运行。

我还在学习(通过做)线程

线程安全不太适合“边做边学”。编写“几乎”始终有效的代码非常容易。如果不幸的话,它会在某些关键操作期间默默地失败,从而导致不正确的结果和非常昂贵的清理费用,可能还包括诉讼。您需要熟悉
危险

以及如何防范它们。

© www.soinside.com 2019 - 2024. All rights reserved.