轮询正确的方式?

时间:2014-04-28 12:15:14

标签: c# multithreading task hardware polling

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

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

此设备没有中断功能。

我的问题:这是正确的方法吗?我应该使用计时器,线程还是任务?我知道民意调查是你们大多数人“讨厌”的事情,但任何建议都是受欢迎的!

3 个答案:

答案 0 :(得分:37)

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包含任务操作的例外,如果它不成功)。

答案 1 :(得分:2)

IMO民意调查无法避免。

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

答案 2 :(得分:0)

可能是:

   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();
            }
        }
    }

查看SpinWaitTask.Delay内部。