Search code examples
c#multithreadingtask-parallel-librarystack-overflow

Run Process on special thread and await result


I had this simple code...

var map = new ReferencedEntityMapAce(uow); ... which worked fine

but now I need to run it on a different thread (with a large stack size due to its recursion) and await its result before continuing.

What is the simplest way of doing this? (I can't see a way of giving Task a specific thread or telling it to create one with a large stack)

Background (if needed): The code above that I had been using for months suddenly starting throwing a stack overflow exception. I believe I have just hit a limit since it is now processing nearly 140k entities with relationships to decide what order they should be saved to initialize a new database. I cannot alter the recursion part - that is in an external third-party library I use with no plans to update it.

I have hacked up test code to prove that it does indeed work when procesed on a large stack thread.


Solution

  • You can use Thread class with the maxStackSize constructor but if you want to keep Task semantic you have to implement custom TaskScheduler like as follows:

    public class BigStackTaskScheduler : TaskScheduler
    {
        private int _stackSize;
    
        public BigStackTaskScheduler(int stackSize)
        {
            _stackSize = stackSize;
        }
    
        // we don't need to keep a tasks queue here
        protected override IEnumerable<Task> GetScheduledTasks()
        {
            return new Task [] { };
        }
    
        protected override void QueueTask(Task task)
        {
            var thread = new Thread(ThreadWork, _stackSize);
            thread.Start(task);
        }
    
        // we aren't going to inline the execution
        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
        {
            QueueTask(task);
            return false;
        }
    
        private void ThreadWork(object obj)
        {
            if (obj is Task task)
                TryExecuteTask(task);
        }
    }
    
    class Program
    {
        async static Task Test()
        {
            var taskFactory = new TaskFactory(
                CancellationToken.None, TaskCreationOptions.DenyChildAttach,
                TaskContinuationOptions.None, new BigStackTaskScheduler(0xffff * 2));
            await taskFactory.StartNew(() => { Console.WriteLine("Task"); });
        }
    
        static void Main(string[] args)
        {
            Test().Wait();
        }
    }
    

    Update: As possible alternative of custom TaskScheduler the TaskCompletionSource can be used:

    class Program
    {
        static Task<TOut> ThreadWithCustomStack<TIn, TOut>(Func<TIn, TOut> action, TIn arg, int stackSize)
        {
            var tcs = new TaskCompletionSource<TOut>();
    
            var thread = new Thread(new ThreadStart(() => 
            {
                try
                {
                    tcs.SetResult(action(arg));
                }
                catch (Exception e)
                {
                    tcs.SetException(e);
                }
            }), stackSize);
    
            thread.Start();
            thread.Join();
    
            return tcs.Task;
        }
    
        async static Task Test()
        {
            var result = await ThreadWithCustomStack(
                arg => { Console.WriteLine("Task"); return arg.ToString(); }, 
                2, 
                0xffff * 2);
        }
    
        static void Main(string[] args)
        {
            Test().Wait();
        }
    }