Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TaskScheduler with limited concurrency blocks itself

I am trying to use the following custom task scheduler to limit concurrent web requests:

I'm using it like this:

const int CONCURRENCY_LEVEL = 2;

static void Main()
{
    TaskFactory factory = new TaskFactory(new LimitedConcurrencyLevelTaskScheduler(CONCURRENCY_LEVEL));

    Task.WaitAll(new[] { factory.StartNew(() => SomeAction("first")), factory.StartNew(() => SomeAction("second")) });

    Console.WriteLine("All tasks ended");        
    Console.Read();
}

static void SomeAction(string task)
{
    Console.WriteLine(task + " task started");
    Console.WriteLine(AnotherTask(task).Result);
    Console.WriteLine(task + " task ended");
}

static async Task<string> AnotherTask(string task)
{
    return await Task.Run(() =>
    {
        Console.WriteLine(task + " inner task started");
        // imitate web requests
        Thread.Sleep(200);
        Console.WriteLine(task + " inner task ended");
        return "ok";
    });
}

The problem is that the inner tasks are using my LimitedConcurrencyLevelTaskScheduler instead of the default TaskScheduler. This leads to self blocking and the output is like this:

second task started
first task started
first inner task started
second inner task started
second inner task ended
first inner task ended

When I change the CONCURRENCY_LEVEL to 3 or change the factory to Task.Factory then obviously everything is working fine and the output is like:

first task started
second task started
first inner task started
second inner task started
second inner task ended
first inner task ended
ok
second task ended
ok
first task ended
All tasks ended

I cannot change the code in SomeAction. What else can I do? Is there a bug in the Microsoft sample?

like image 663
Adassko Avatar asked Feb 06 '15 20:02

Adassko


2 Answers

Specify TaskCreationOptions.HideScheduler when starting tasks from your custom task scheduler:

Task.WaitAll(new[] 
{
   factory.StartNew(() => SomeAction("first"),  TaskCreationOptions.HideScheduler), 
   factory.StartNew(() => SomeAction("second"), TaskCreationOptions.HideScheduler)
});

From MSDN:

HideScheduler Prevents the ambient scheduler from being seen as the current scheduler in the created task. This means that operations like StartNew or ContinueWith that are performed in the created task will see Default as the current scheduler.

like image 60
Douglas Avatar answered Oct 20 '22 15:10

Douglas


Try

static async Task<string> AnotherTask(string task)
{
    return await Task.Run(() =>
    {
        Console.WriteLine(task + " inner task started");
        // imitate web requests
        Thread.Sleep(200);
        Console.WriteLine(task + " inner task ended");
        return "ok";
    }).ConfigureAwait(false); //<--------
}
like image 2
EZI Avatar answered Oct 20 '22 17:10

EZI