投票方式正确

本文关键字:方式正 | 更新日期: 2023-09-27 18:00:35

我是一名软件/硬件工程师,在C和嵌入式技术方面有着丰富的经验。目前,我正忙于用C#(.NET)编写一些使用硬件进行数据采集的应用程序。现在下面,对于我燃烧,问题:

例如:我有一台机器,它有一个末端开关,用于检测轴的最终位置。现在我使用USB数据采集模块来读取数据。目前我正在使用线程来连续读取端口状态。

此设备上没有中断功能。

我的问题是:这是正确的方式吗?我应该使用定时器、线程还是任务?我知道投票是你们大多数人"讨厌"的事情,但任何建议都是受欢迎的!

投票方式正确

IMO,这在很大程度上取决于您的确切环境,但首先,在大多数情况下,您不应该再使用Threads。Tasks是更方便、更强大的解决方案。

  • 轮询频率低:计时器+Tick事件中的轮询:
    计时器很容易操作和停止。无需担心后台运行的线程/任务,但处理发生在主线程中

  • 中轮询频率:Task+await Task.Delay(delay):
    await Task.Delay(delay)不会阻塞线程池线程,但由于上下文切换,最小延迟约为15ms

  • 高轮询频率:Task+Thread.Sleep(delay)
    在1ms延迟时可用-我们实际上这样做是为了轮询我们的USB测量设备

这可以通过以下方式实现:

int delay = 1;
var cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
var listener = Task.Factory.StartNew(() =>
{
    while (true)
    {
        // poll hardware
        Thread.Sleep(delay);
        if (token.IsCancellationRequested)
            break;
    }
    // cleanup, e.g. close connection
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

在大多数情况下,您可以只使用Task.Run(() => DoWork(), token),但没有过载来提供TaskCreationOptions.LongRunning选项,该选项"告诉"任务调度程序不要使用普通的线程线程
但正如您所看到的,Tasks更容易处理(并且await可以,但不适用于此处)。特别是在这个实现中,"停止"只是从代码中的任何位置调用cancellationTokenSource.Cancel()

您甚至可以在多个操作中共享此令牌,并立即停止它们。此外,当令牌被取消时,尚未启动的任务也不会启动。

您还可以将另一个操作附加到任务上,以便在一个任务之后运行:

listener.ContinueWith(t => ShutDown(t));

然后在侦听器完成后执行此操作,您可以进行清理(如果未成功,t.Exception包含tasks操作的异常)。

IMO轮询无法避免。

您可以创建一个模块,该模块具有独立的线程/Task,它将定期轮询端口。根据数据的变化,此模块将引发将由消费应用程序处理的事件

可能是:

   public async Task Poll(Func<bool> condition, TimeSpan timeout, string message = null)
    {
        // https://github.com/dotnet/corefx/blob/3b24c535852d19274362ad3dbc75e932b7d41766/src/Common/src/CoreLib/System/Threading/ReaderWriterLockSlim.cs#L233 
        var timeoutTracker = new TimeoutTracker(timeout);
        while (!condition())
        {
            await Task.Yield();
            if (timeoutTracker.IsExpired)
            {
                if (message != null) throw new TimeoutException(message);
                else throw new TimeoutException();
            }
        }
    }

查看SpinWait或Task。延迟内部。

我一直在考虑这个问题,您可能会在利用Tasks和Func、Action的基础上构建一个抽象层,轮询服务将Func、Action和轮询间隔作为args。这将使这两种功能的实现保持独立,同时将它们开放以注入到轮询服务中。

例如,你会有这样的东西作为你的投票类

public class PollingService {
    public void Poll(Func<bool> func, int interval, string exceptionMessage) {
        while(func.Invoke()){
            Task.Delay(interval)
        }
        throw new PollingException(exceptionMessage)
    }
    public void Poll(Func<bool, T> func, T arg, int interval, string exceptionMessage) 
    {
        while(func.Invoke(arg)){
            Task.Delay(interval)
        }
        throw new PollingException(exceptionMessage)
    }
}