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>
?
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.
Differences Between Task And ThreadThe Thread class is used for creating and manipulating a thread in Windows. A Task represents some asynchronous operation and is part of the Task Parallel Library, a set of APIs for running tasks asynchronously and in parallel. The task can return a result.
While Task. Run turns something synchronous into a Task (by running it on a separate thread), TaskCompletionSource turns something that is already asynchronous into a Task .
To start a task in C#, follow any of the below given ways. Use a delegate to start a task. Task t = new Task(delegate { PrintMessage(); }); t. Start();
Run is misused to run IO blocking tasks. Although the code will work just fine (e.g UI not not freeze) but it is still a wrong approach. This is because Task. Run will still block a thread from thread pool the entire time until it finishes the method.
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.
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With