如何在.net 3.5中获得等效的Task ?
我有一些使用Task
代码,它推迟了短时间内从串行读操作返回结果,如下所示:
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
,因此我必须以某种方式重构此代码。
如何在不使用Task
情况下实现相同的结果?
澄清
虽然我展示的具体代码恰好是一个定时延迟,但我的用法并不仅限于延迟。 可能还有其他情况我想立即启动一些“长时间运行”的轮询任务。 典型情况是I / O绑定操作,例如,定期查询连接到串行端口的设备,然后在满足某些条件时引发事件。
虽然我展示的具体代码恰好是一个定时延迟,但我的用法并不仅限于延迟。 可能还有其他情况我想立即启动一些“长时间运行”的轮询任务。 典型情况是I / O绑定操作,例如,定期查询连接到串行端口的设备,然后在满足某些条件时引发事件。
使用C#2.0 – 4.0对这种场景进行编码的一个值得注意的方便的方法就是使用自驱动的IEnumerable
和yield
。 它允许实现异步状态机,类似于C#5.0的async/await
。 这样,您可以为异步逻辑保留方便的线性代码流。 所有C#语言代码控制语句都有效(除了你不能从try/catch
内部yield return
)。
例如,带有计时器的控制台应用程序:
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
的处理程序nextStep
,类似于上面的计时器回调。 在事件发生后,代码将在相应的yield return
后继续。
有很多实现利用这种方法,例如Jeffrey Richter的AsyncEnumerator
。
您可以使用NuGet提供的以下任一软件包:
-
您可以在NuGet上使用TaskParallelLibrary包。 此程序包具有强名称,是.NET 4的.NET任务并行库的.NET 3.5后端程序,包含在Reactive Extensions中。
-
您可以在NuGet上使用System.Threading.Tasks.Unofficial包。 此替代实现使用Mono代码库而不是Microsoft实现。 请注意,此程序包中包含的程序集没有强名称,因此如果您的库使用强名称,则此选项不可用。
使用Timer
(实际上是内部实现Delay
方式)。
private static HashSet timers = new HashSet (); 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的任何方法,除非他们有一些令人信服的理由不这样做。
使用ThreadPool执行方法:
http://msdn.microsoft.com/en-us/library/kbf0f1ct(v=vs.110).aspx
编辑Servy:
任务并行库和PLINQ的默认调度程序使用.NET Framework ThreadPool来排队和执行工作。 在.NET Framework 4中,ThreadPool使用System.Threading.Tasks.Task类型提供的信息来有效地支持并行任务和查询经常表示的细粒度并行性(短期工作单元)。
编辑2(由于负面反馈):
此代码完全符合OP的要求:
void ReturnResponseAfterAShortDelay(WaitHandle cancellationToken) { log.InfoFormat("Deferring response for {0} ms", Settings.Default.TimeoutMs); ThreadPool.RegisterWaitForSingleObject(cancellationToken, () => { if (!cancellationToken.WaitOne(0)) { ReturnWhateverHasArrived(); } }, null, Settings.Default.TimeoutMs, true); }