Search code examples
c#asynchronous.net-3.5task-parallel-librarytask

How can I get the equivalent of Task<T> in .net 3.5?


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.


Solution

  • 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.