【问题标题】:Implementing a permanent thread that manages a resource and other threads that requests this thread to do some task and return a result实现一个管理资源的永久线程和请求该线程执行某些任务并返回结果的其他线程
【发布时间】:2022-06-11 00:08:26
【问题描述】:

我希望拥有一个具有永久生命周期的线程(把它想象成一个进程)并管理资源(在我的例子中是一个关键部分,它是一个通过串行端口访问的 3G 调制解调器)。

其他线程会通过一个方法向这个线程发送请求,并等待它发送响应或超时。

我可以使用 Qt C++ 框架来实现这一点,但我没有找到在 C# 中实现它的简单方法(我之前已经问过一个问题,有人告诉我有关线程上下文的信息,但他没有回答我的问题)问题),我必须编写类似 Qt 的信号/插槽系统的东西,这是一项乏味的任务。

一位同事让我看一下 AsyncEx,但我没有找到说明我想要实现的目标的示例,我不确定它是否会有所帮助(async/await 也不会)。

这真的让我很生气,因为我有一个使用锁的 C# 错误代码(并发错误,不,不是我写的),我想使用这个线程模型来重构它。

令人难以置信的是,只有 Qt 做了一些强大的事情来处理这种情况(线程感知信号/槽和事件循环),其他的只提供无用的垃圾。

更新:在 Qt 中,我们不能调用带有超时的方法。它会阻塞直到另一个线程发送回复,根据这个https://bugreports.qt.io/browse/QTBUG-63870,最好执行一个非阻塞请求......

更新 2:按照 JonasH 的回答,我做了这个例子:https://github.com/embeddedmz/message_passing_on_csharp 这对于与我对线程模型(消息传递)有相同观点的人可能有用,在我看来,线程模型的危害要小得多其他(你知道多线程程序在大多数情况下运行良好,对我来说它是二进制的,它要么一直运行要么什么都不运行)。

当我的同事在未来的更新中向我展示时,我将分享另一个解决方案。

【问题讨论】:

  • 真的需要一个标志吗?如果一次只有一个线程可以访问资源,肯定没问题吗?
  • Re,“令人难以置信的是,只有 Qt 做了一些强大的事情来处理这种情况。”你应该相信你的直觉。某些断言可能似乎令人难以置信的一个原因是它完全是胡扯。您描述的用例与多线程本身一样古老。实际上,每个多线程库都提供了编写长时间运行的“服务器”线程以代表其他线程管理某些资源所需的工具。
  • 对不起,我想我不明白你想“完全像 Qt 提供的工具一样”解决问题,而且我不知道 Qt 的高质量 C# 绑定可能很难找到。我无法帮助您,因为我真的不知道“Qt 提供的设施”。我从来没有在任何项目中亲自编写过调用 Qt API 的代码。
  • 看看微软的反应式框架和EventLoopScheduler的实现。

标签: c# multithreading async-await concurrency


【解决方案1】:

一种方法是共享LimitedConcurrencyLevelTaskScheduler(参见示例)。这可以确保在任何时候只有一个线程对资源具有独占访问权,而在不需要资源时不需要阻塞线程。

自己编写此功能的典型方法是使用blocking collection。 IE。工作线程将在 GetConsumingEnumerable 上运行 foreach 循环并处理每个项目。一个非常简化的示例可能类似于:

        private TResource resource;
        private BlockingCollection<Action<TResource>> queue = new();
        public void Start() => Task.Run(ThreadMain);
        public Task<T> Enqueue<T>(Func<TResource, T> method)
        {
            var tcs = new TaskCompletionSource<T>();
            queue.Add(r => tcs.SetResult(method(r)));
            return tcs.Task;
        }
        private void ThreadMain()
        {
            foreach (var action in queue.GetConsumingEnumerable())
            {
                action(resource);
            }
        }

但您需要添加错误处理、取消、停止线程等内容。

另一种选择可能是限制为 1 的 semaphoreSlim,并将 WaitAsync 与 async/await 一起使用以确保独占访问。如果这对您很重要,我认为信号量不会保证任何特定的顺序。如果工作需要时间,您还可能会冒着阻塞 UI 线程的风险。

还有像 DataFlow 这样的框架可能会有所帮助,但我对它不够熟悉,无法提供任何建议。

有关基于任务的编程的介绍,请参阅Task-based asynchronous programmingAsynchronous programming with async and await

【讨论】:

  • @Aminos 使用Task,如示例所示。大多数线程之间的通信应该通过任务完成。使用 SemaphoreSlim 或 LimitedConcurrencyTaskScheduler 您将自动获取任务,使用单独的线程您需要使用 TaskCompletionSource 来创建任务。
  • +1。更完善的实现会在catch 调用中出现method() 错误,并通过SetException 方法传播此错误。
  • @Aminos Task 实际上有一个.Wait() 方法。但是如果你在 .net 中做任何涉及并发的任何事情,你真的需要了解任务和异步/等待。在答案中添加了一些链接,但是如果您环顾四周,还有很多其他资源。
  • @Aminos Oups,应该是queue.Add(r =&gt; tcs.SetResult(method(r)))
  • 并摆脱 while 循环,它不会按您的意愿工作。使用BlockingCollection&lt;T&gt;.CompleteAdding 完成队列,允许foreach 循环退出。
猜你喜欢
  • 2022-06-15
  • 2019-08-18
  • 2021-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多