How can I get the equivalent of Task in .net 3.

2019-02-03 12:58发布

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.

5条回答
放我归山
2楼-- · 2019-02-03 13:11

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>()
查看更多
我只想做你的唯一
3楼-- · 2019-02-03 13:12

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.

查看更多
叼着烟拽天下
4楼-- · 2019-02-03 13:16

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.

查看更多
疯言疯语
5楼-- · 2019-02-03 13:24

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.

查看更多
Fickle 薄情
6楼-- · 2019-02-03 13:36

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);
    }
查看更多
登录 后发表回答