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

5

10

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.

Bicipital answered 19/3, 2014 at 19:32 Comment(1)
nuget.org/packages/TaskParallelLibrarySimmons
K
4

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.

Kathiekathleen answered 20/3, 2014 at 1:48 Comment(6)
mre.WaitOne() still blocks the current thread, though, according to MSDN,Dupree
@Nyerguds, mre.WaitOne() is used here on the top level of the call chain (Main). It's a console app and you have to block there, otherwise the program will just end. Otherwise, the key point here is WorkAsync and it's non-blocking.Kathiekathleen
I see. Not sure I see any use in multithreading in a command line app though. I was kind of hoping to apply something similar to a forms app.Dupree
Where we put our process function, ı couldnt understand main funtion @KathiekathleenDallas
@IchigoKurosaki, not sure I understand the question.. what process function?Kathiekathleen
@Kathiekathleen when I looked at your answers with T.Todua and Servy, it seemed a bit difficult to me. Is it possible for you to add an example like their answer? I couldn't implement your code :S sorry about that, I asked for an example because I couldn't understand it.Dallas
V
19

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.

Vasili answered 19/3, 2014 at 21:12 Comment(3)
Awesome, that someone ported TPL to .NET 3.5, so I can use it in Unity3D! :-)Helmsman
@Helmsman If you use a newer C# compiler (from Visual Studio 2012 or newer), you can even reference TunnelVisionLabs.Threading and get async/await support for .NET 3.5.Vasili
Good to know! But Unity comes with its own compiler and I'm afraid its not capable of that, but will give it a try! Thx! :-)Helmsman
S
4

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.

Seacock answered 19/3, 2014 at 19:49 Comment(10)
GC will collect your new timer object since you do not keep a reference. Also you do not offer cancel functionality.Succursal
@Succursal technically, yes, you need to hold onto the Timer. As for cancellation, you can simply check if cancellation is requested from with the action that calls this method.Seacock
or you can simply use ThreadPool as I exemplified in the update on my answer and do not complicate your action with cancellation code since that is kinda bad design ...Succursal
@Succursal But then you're creating a thread pool thread that is going to do nothing but synchronously wait for a period of time. That is not an effective use of resources. If the OP wishes to incorporate his cancellation directly into this method, he's certainly welcome to.Seacock
Please check latest update to my answer. ThreadPool has other functionality besides queuing work items, functionality used to implement the Delay in tasks ...Succursal
@Succursal It's still going to block the thread pool thread for that period of time, as per that method's documentation, as opposed to an asynchronous approach that can consume no threads at all while waiting for the asynchronous operation to finish.Seacock
A simple test will show that not each call for RegisterWaitForSingleObject will use a thread ... they all seem to work on the same thread ...Succursal
Servy, your code didn't work for me either, and have posted another version: https://mcmap.net/q/1049074/-how-can-i-get-the-equivalent-of-task-lt-t-gt-in-net-3-5 if you'd like you can merge that answer and I'll delete my answer.Cleodal
Doesn't the code include race conditions for delay:=0? NullReferenceException for timer.Dispose() if action has executed before timer has been set. timers.Remove(timer) may execute before timers.Add(timer) leaving the timer in the HashSet.Notice
@PaulB., do we need to swap timers.Add(timer); timers.Remove(timer); location?,Dallas
K
4

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.

Kathiekathleen answered 20/3, 2014 at 1:48 Comment(6)
mre.WaitOne() still blocks the current thread, though, according to MSDN,Dupree
@Nyerguds, mre.WaitOne() is used here on the top level of the call chain (Main). It's a console app and you have to block there, otherwise the program will just end. Otherwise, the key point here is WorkAsync and it's non-blocking.Kathiekathleen
I see. Not sure I see any use in multithreading in a command line app though. I was kind of hoping to apply something similar to a forms app.Dupree
Where we put our process function, ı couldnt understand main funtion @KathiekathleenDallas
@IchigoKurosaki, not sure I understand the question.. what process function?Kathiekathleen
@Kathiekathleen when I looked at your answers with T.Todua and Servy, it seemed a bit difficult to me. Is it possible for you to add an example like their answer? I couldn't implement your code :S sorry about that, I asked for an example because I couldn't understand it.Dallas
C
0

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>()
Cleodal answered 9/11, 2018 at 7:40 Comment(1)
What's the benefit of UInt32.MaxValue-10 when we have Timeout.Infinite for one-time execution?Notice
S
-2

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);
    }
Succursal answered 19/3, 2014 at 19:35 Comment(8)
Yes, I'm aware of that, however this assumes that what the OP wants to do is just execute some code in a thread pool thread. That's not what his code is doing, and this is not the appropriate solution to use for him to solve his problem. It looks like you looked at the title but didn't actually look at the code in the question at all.Seacock
@Seacock From the question body you can see that OP would have use task if available. Default task is just a layer over ThreadPool so this is exactly what he wants - the Task<T> functionality from 4.0 in 3.5Succursal
@Succursal - The code in the OP uses Task.Delay which probably avoids using the threadpool, since it is just waiting for a timeout and not doing any other work.Simmons
No, it's not what he wants. He's not just executing code to run in a thread pool thread. He's using Delay to execute some code after a given interval of time, which is a problem that ThreadPool doesn't solve. You seem to not have really grasped the true capabilities of Task, because it goes way beyond just executing code in a thread pool thread.Seacock
Yeah, I get what you are saying. However timer still uses the tread pool and using Thread.Sleep when using QueueUserWorkItem will have basically the same result(just will hold one of the threads blocked). PS: your code in the answer does not work ...Succursal
@Succursal No, that's not what it does. Not at all. It is going to be a properly asynchronous operation that doesn't use any threads at all when waiting, not even thread pool threads.Seacock
For what it's worth, although the specific code I showed happens to be a timed delay, there may be other cases where I want to start some 'long running' task immediately. My typical situation would be an I/O bound operation, for example something the periodically queries a device attached to the serial port.Bicipital
This runs the delegate if the handle is signalled, which does not happen in the task code in the OP.Simmons

© 2022 - 2024 — McMap. All rights reserved.