可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have some code that is using Task<T>
which defers returning a result from a serial read operation for a short time, like this:
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();
}
The idea behind this code is to return the result when no new characters have arrived for a specified interval.
However, due to factors outside of my control, I must use .NET 3.5, which prevents me using Task<T>
, so I have to refactor this code somehow.
How can I achieve the same result, without using Task<T>
?
Clarification
Although the specific code I showed happens to be a timed delay, my usage isn't limited to delaying things. There may be other cases where I want to start some 'long running' polling task immediately. A typical situation would be an I/O bound operation, for example something the periodically queries a device attached to the serial port and then raises an event when some condition is met.
回答1:
Although the specific code I showed happens to be a timed delay, my
usage isn't limited to delaying things. There may be other cases where
I want to start some 'long running' polling task immediately. A
typical situation would be an I/O bound operation, for example
something the periodically queries a device attached to the serial
port and then raises an event when some condition is met.
A notable and handy hack for coding such scenarios with C# 2.0 - 4.0 is to use self-driven IEnumerable
and yield
. It allows to implement an asynchronous state machine, similar to async/await
of C# 5.0. This way you keep the convenient linear code flow for your asynchronous logic. All C# language code control statements work (besides you can't do yield return
from inside try/catch
).
For example, a console app with a timer:
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();
}
}
}
Any event could be wrapped with a handler which would call nextStep
, similar to the above timer callback. The code would continue after the corresponding yield return
, upon the event.
There are quite a few implementations taking advantage of this approach, e.g., Jeffrey Richter's AsyncEnumerator
.
回答2:
You can use either of the following packages available through NuGet:
You can use the TaskParallelLibrary package on NuGet. This package has a strong name and is a .NET 3.5 back-port of the .NET Task Parallel Library for .NET 4 that was included in the Reactive Extensions.
You can use the System.Threading.Tasks.Unofficial package on NuGet. This alternative implementation uses the Mono codebase instead of the Microsoft implementation. Note that the assembly included in this package does not have a strong name, so if your library uses strong names then this is not an option.
回答3:
Use a Timer
(which is actually how Delay
is implemented internally).
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);
}
To your edit, if you're using an asynchronous application built on top of asynchronous IO, then that asynchronous IO will already expose some method of asynchrony. It could be an event based model, it could accept a callback, it could be using IAsyncResult
, etc. Task
is yet another possible approach to asynchronous programming, and you're certainly capable of translating any approach to any other approach, if one is preferable to you, but generally people tend to stick with whatever method the underlying IO they are performing uses, unless they have some compelling reason to do otherwise.
回答4:
Based on @Servy's answer, a bit modified one which worked for me:
ExecuteAfter(() => MessageBox.Show("hi"), 1000 );
Code:
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>()
回答5:
Use ThreadPool to execute the method:
http://msdn.microsoft.com/en-us/library/kbf0f1ct(v=vs.110).aspx
Edit for Servy:
The default scheduler for Task Parallel Library and PLINQ uses the .NET Framework ThreadPool to queue and execute work. In the .NET Framework 4, the ThreadPool uses the information that is provided by the System.Threading.Tasks.Task type to efficiently support the fine-grained parallelism (short-lived units of work) that parallel tasks and queries often represent.
Edit 2(because of negative feedback):
This code does exactly what the OP wants:
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);
}