我是一名软硬件工程师,有丰富的C语言和嵌入式技术经验。目前我正忙于编写一些使用硬件进行数据采集的C# (.NET)应用程序。现在,对我来说最紧迫的问题是:
例如: 我有一台机器,它有一个终端开关,用于检测轴的最终位置。现在我正在使用USB数据采集模块读取数据。目前,我正在使用线程来连续读取端口状态。
该设备没有中断功能。
我的问题是:这是正确的方式吗?应该使用定时器,线程还是任务?我知道轮询是大多数人“讨厌”的东西,但欢迎任何建议!
我是一名软硬件工程师,有丰富的C语言和嵌入式技术经验。目前我正忙于编写一些使用硬件进行数据采集的C# (.NET)应用程序。现在,对我来说最紧迫的问题是:
例如: 我有一台机器,它有一个终端开关,用于检测轴的最终位置。现在我正在使用USB数据采集模块读取数据。目前,我正在使用线程来连续读取端口状态。
该设备没有中断功能。
我的问题是:这是正确的方式吗?应该使用定时器,线程还是任务?我知道轮询是大多数人“讨厌”的东西,但欢迎任何建议!
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
包含任务动作的异常,如果不成功)。
无法避免IMO轮询。
您可以创建一个模块,带有独立的线程/任务,定期轮询端口。根据数据的变化,此模块将引发事件,由使用应用程序处理。
可能是:
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.Delay的内部原理。
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)
}
}
System.Threading.Timer
来进行轮询 - 这样可以避免创建新线程。 - Matthew Watson