Thread.Sleep into a Task

1.6k views Asked by At

I've the following code:

    static void Main(string[] args)
    {
        IEnumerable<int> threadsIds = Enumerable.Range(1, 1000);
        DateTime globalStart = DateTime.Now;

        Console.WriteLine("{0:s.fff} Starting tasks", globalStart);
        Parallel.ForEach(threadsIds, (threadsId) =>
        {
            DateTime taskStart = DateTime.Now;
            const int sleepDuration = 1000;
            Console.WriteLine("{1:s.fff} Starting task {0}, sleeping for {2}", threadsId, taskStart, sleepDuration);
            Thread.Sleep(sleepDuration);
            DateTime taskFinish = DateTime.Now;
            Console.WriteLine("{1:s.fff} Ending task {0}, task duration {2}", threadsId, taskFinish, taskFinish- taskStart);

        });
        DateTime globalFinish= DateTime.Now;
        Console.WriteLine("{0:s.fff} Tasks finished. Total duration: {1}", globalFinish, globalFinish-globalStart);
        Console.ReadLine();
    }

Currently when I run it, it takes ~60seconds to run it. For what I understand, it's because .Net doesn't create one thread per task but some threads for all the Tasks, and when I do the Thread.Sleep, I prevent this thread to execute some other tasks.

In my real case, I've some work to do in parallel, and in case of failure, I've to wait some amount of time before trying again.

I'm looking something else than the Thread.Sleep, that would allow other tasks to run during the "sleep time" of other tasks.

Unfortunately, I'm currently running .Net 4, which prevent me to use async and await(which I guess could have helped me in this case.

Ps, I got the same results by:

  • putting Task.Delay(sleepDuration).Wait()
  • Not using Parallel.Foreach, but a foreach with a Task.Factory.StartNew

Ps2, I know that I can do my real case differently, but I'm very interessted to understand how it could be achieved that way.

1

There are 1 answers

11
Andrey Nasonov On

You are on the right path. Task.Delay(timespan) is the solution for your problem. Since you cannot use async/await, you have to write a bit more code to achieve the desired result.

Think about using Task.ContinueWith() method, for example:

Task.Run(() => { /* code before Thread.Sleep */ })
.ContinueWith(task => Task.Delay(sleepDuration)
    .ContinueWith(task2 => { /* code after Thread.Sleep */ }));

Also you will need create a class to make local method variables accessible across subtasks.

If you want to create a task that will run polling every second some condition, you could try the following code:

Task PollTask(Func<bool> condition)
{
    TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
    PollTaskImpl(tcs, condition);
    return tcs.Task;
}

void PollTaskImpl(TaskCompletionSource<bool> tcs, Func<bool> condition)
{
    if (condition())
        tcs.SetResult(true);
    else
        Task.Delay(1000).ContinueWith(_ => PollTaskImpl(tcs, condition));
}

Don't worry about creating new task every second - ContinueWith and async/await methods do the same thing internally.