0

I wrote my custom TaskScheduler:

public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{

    private BlockingCollection<Task> _tasks = new BlockingCollection<Task>();
    private List<Thread> _threads;
    private bool work = true;

    public LimitedConcurrencyLevelTaskScheduler(int maxConcurrencyLevel)
    {

        _threads = new List<Thread>();
        for (int i = 0; i < maxConcurrencyLevel; i++)
        {
            _threads.Add(new Thread(() =>
                                        {
                                            while (work)
                                            {
                                                TryExecuteTask(_tasks.Take());
                                            }
                                        }) { IsBackground = true, Name = "TaskShedulerThread#" + i });
        }
        foreach (var thread in _threads)
        {
            thread.Start();
        }

    }

    protected override void QueueTask(Task task)
    {
        _tasks.Add(task);
    }

    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        return !_threads.Contains(Thread.CurrentThread) && TryExecuteTask(task);
    }

    public override int MaximumConcurrencyLevel { get { return 1; } }

    protected override IEnumerable<Task> GetScheduledTasks()
    {
        return _tasks.ToArray();
    }

    public void Dispose()
    {
        if (_threads != null)
        {
            _tasks.CompleteAdding();
            work = false;

            _tasks.Dispose();
            _tasks = null;
            _threads = null;
        }
    }
}

And use it in this way:

    static void Main(string[] args)
    {
        var taskScheduller = new LimitedConcurrencyLevelTaskScheduler(1);
        Thread.CurrentThread.Name = "MainThread";
        var taskFactory = new TaskFactory(taskScheduller);
        var tasks = new Task[100];
        for (int i = 0; i < 100; i++)
        {
            tasks[i] = taskFactory.StartNew(() => Console.WriteLine(String.Format("Call in {0}", Thread.CurrentThread.Name)));
        }
        Task.WaitAll(tasks);
    }

Programm's output:

Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in MainThread
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in MainThread
Call in MainThread
Call in MainThread
Call in MainThread
Call in MainThread
Call in MainThread
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
Call in TaskShedulerThread#0
...

Why my tasks runned in main thread?

4

1 回答 1

0

Tasks are getting executed on the main thread due to a TPL feature known as task inlining. When a thread calls WaitAll (or any similar method) on a task that has not yet commenced execution, TPL may allow the said thread to attempt to execute the pending task itself, rather than block until it is executed by one of the task scheduler's worker threads.

This behaviour can be controlled by overriding the TryExecuteTaskInline method of your task scheduler, forbidding it from executing any tasks unless the current thread happens to be a worker thread.

protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
    return !_threads.Contains(Thread.CurrentThread) && TryExecuteTask(task);
}

In your implementation, you just need to remove the negation before the Contains check:

protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
    return _threads.Contains(Thread.CurrentThread) && TryExecuteTask(task);
}
于 2013-09-25T10:11:07.300 回答