Task<T>
を使用しているコードがあります。これは、次のように、シリアル読み取り操作からの結果を短時間返すのを延期します。
void ReturnResponseAfterAShortDelay()
{
if (delayedResponseCancellationTokenSource != null)
delayedResponseCancellationTokenSource.Cancel(); // Cancel any pending operations and start a new one.
delayedResponseCancellationTokenSource = new CancellationTokenSource();
log.InfoFormat("Deferring response for {0} ms", Settings.Default.TimeoutMs);
Task.Delay(Properties.Settings.Default.TimeoutMs, delayedResponseCancellationTokenSource.Token)
.ContinueWith((continuation) => ReturnWhateverHasArrived(), TaskContinuationOptions.NotOnCanceled)
.Start();
}
このコードの背後にある考え方は、指定された間隔で新しい文字が到着しなかったときに結果を返すことです。
ただし、制御できない要因により、.NET 3.5を使用する必要があります。これにより、Task<T>
を使用できなくなります。そのため、このコードを何らかの方法でリファクタリングする必要があります。
Task<T>
を使用せずに同じ結果を得るにはどうすればよいですか?
私が示した特定のコードはたまたま時限遅延ですが、私の使用法は物事を遅らせることに限定されていません。 「長時間実行中」のポーリングタスクをすぐに開始したい場合もあります。典型的な状況は、I/Oバウンド操作です。たとえば、シリアルポートに接続されたデバイスに定期的にクエリを実行し、何らかの条件が満たされたときにイベントを発生させます。
私が示した特定のコードはたまたま時限遅延ですが、私の使用法は物事を遅らせることに限定されていません。 「長時間実行中」のポーリングタスクをすぐに開始したい場合もあります。典型的な状況は、I/Oバウンド操作です。たとえば、シリアルポートに接続されたデバイスに定期的にクエリを実行し、何らかの条件が満たされたときにイベントを発生させます。
このようなシナリオをC#2.0〜4.0でコーディングするための注目すべき便利なハックは、自己駆動型のIEnumerable
およびyield
を使用することです。これにより、C#5.0のasync/await
と同様の非同期ステートマシンを実装できます。このようにして、非同期ロジックの便利な線形コードフローを維持します。すべてのC#言語コード制御ステートメントが機能します(yield return
の内部からtry/catch
を実行できないことを除いて)。
たとえば、タイマー付きのコンソールアプリ:
using System;
using System.Collections;
using System.Threading;
namespace ConsoleApplication_22516303
{
class Program
{
class AsyncLogic
{
public EventHandler Completed = delegate { };
IEnumerable WorkAsync(Action NeXTSTEP)
{
using (var timer = new System.Threading.Timer(_ => NeXTSTEP()))
{
timer.Change(0, 500);
var tick = 0;
while (tick < 10)
{
// resume upon next timer tick
yield return Type.Missing;
Console.WriteLine("Tick: " + tick++);
}
}
this.Completed(this, EventArgs.Empty);
}
public void Start()
{
IEnumerator enumerator = null;
Action NeXTSTEP = () => enumerator.MoveNext();
enumerator = WorkAsync(NeXTSTEP).GetEnumerator();
NeXTSTEP();
}
}
static void Main(string[] args)
{
var mre = new ManualResetEvent(false);
var asyncLogic = new AsyncLogic();
asyncLogic.Completed += (s, e) => mre.Set();
asyncLogic.Start();
mre.WaitOne();
Console.WriteLine("Completed, press Enter to exit");
Console.ReadLine();
}
}
}
上記のタイマーコールバックと同様に、任意のイベントをNeXTSTEP
を呼び出すハンドラーでラップできます。コードは、イベント時に、対応するyield return
の後に続きます。
このアプローチを利用する実装はかなりあります。たとえば、 JeffreyRichterのAsyncEnumerator
です。
NuGetから入手できる次のパッケージのいずれかを使用できます。
NuGetで TaskParallelLibrary パッケージを使用できます。このパッケージには厳密な名前があり、ReactiveExtensionsに含まれていた.NET4用の.NETTask ParallelLibraryの.NET3.5バックポートです。
NuGetで System.Threading.Tasks.Unofficial パッケージを使用できます。この代替実装では、Microsoft実装の代わりにMonoコードベースを使用します。このパッケージに含まれているアセンブリには厳密な名前がないため、ライブラリで厳密な名前が使用されている場合、これはオプションではないことに注意してください。
Timer
を使用します(これは実際にはDelay
が内部で実装される方法です)。
private static HashSet<Timer> timers = new HashSet<Timer>();
public static void ExecuteAfter(Action action, TimeSpan delay)
{
Timer timer = null;
timer = new System.Threading.Timer(s =>
{
action();
timer.Dispose();
lock (timers)
timers.Remove(timer);
}, null, (long)delay.TotalMilliseconds, Timeout.Infinite);
lock (timers)
timers.Add(timer);
}
あなたの編集では、非同期IOの上に構築された非同期アプリケーションを使用している場合、その非同期IOは、すでに何らかの非同期メソッドを公開しています。イベントベースのモデルである可能性があります。コールバックを受け入れます。IAsyncResult
などを使用している可能性があります。Task
は非同期プログラミングへのさらに別の可能なアプローチであり、他のアプローチに変換することは確かに可能です。あなたよりも望ましいですが、一般的に、他の方法でやむを得ない理由がない限り、基本的なIO使用を実行している方法に固執する傾向があります。
@Servyの回答に基づいて、私のために働いた少し変更されたもの:
ExecuteAfter(() => MessageBox.Show("hi"), 1000 );
コード:
public static void ExecuteAfter(Action action, int milliseconds)
{
System.Threading.Timer timer = null;
timer = new System.Threading.Timer(s =>
{
action();
timer.Dispose();
lock (timers)
timers.Remove(timer);
}, null, milliseconds, UInt32.MaxValue-10);
lock (timers)
timers.Add(timer);
}
private static HashSet<System.Threading.Timer> timers = new HashSet<System.Threading.Timer>()