私はCおよび組み込み技術のかなりの経験を持つソフトウェア/ハードウェアエンジニアです。現在、データ収集にハードウェアを使用しているC#(.NET)でいくつかのアプリケーションを作成するのに忙しくしています。今、私にとって、次の質問:
例:軸の最終位置を検出するためのエンドスイッチを備えたマシンがあります。現在、USBデータ取得モジュールを使用してデータを読み取っています。現在、スレッドを使用してポートステータスを継続的に読み取ります。
このデバイスには割り込み機能はありません。
私の質問:これは正しい方法ですか?タイマー、スレッド、またはタスクを使用する必要がありますか?ポーリングは皆さんの多くが「嫌い」なものですが、提案は大歓迎です!
IMO、これはあなたの正確な環境に大きく依存しますが、最初に-ほとんどの場合、スレッドを使用しないでください。 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
ableですが、ここでは適用されません)。特に「停止」とは、この実装でコードのどこからでもcancellationTokenSource.Cancel()
を呼び出すことです。
このトークンを複数のアクションで共有して、一度に停止することもできます。また、トークンがキャンセルされても、まだ開始されていないタスクは開始されません。
別のアクションをタスクに添付して、1つのタスクの後に実行することもできます。
_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 内部を調べます。